I am trying to make an Asteroids game clone in JavaFX. So far, I have been able to draw the ship and asteroids onto the screen (where Rectangles represent them, for now). I have also implemented movement for the ship, and randomized movements for the asteroids.
I am having trouble implementing the code needed to bounce the asteroids off each other. The current method that does the collision checking (called checkAsteroidCollisions) is bugged in that all asteroids start stuttering in place. They don't move, but rather oscillate back and forth in place rapidly. Without the call to this method, all asteroids begin moving normally and as expected.
Instead, I want each asteroid to move freely and, when coming into contact with another asteroid, bounce off each other like in the actual Asteroids game.
MainApp.java
import java.util.ArrayList;
import java.util.HashSet;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
public class MainApp extends Application {
private static final int WIDTH = 700;
private static final int HEIGHT = 900;
private static final int NUM_OF_ASTEROIDS = 12;
private static final Color ASTEROID_COLOR = Color.GRAY;
private static final Color PLAYER_COLOR = Color.BLUE;
private Player player;
private ArrayList<Entity> asteroids;
long lastNanoTime; // For AnimationTimer
HashSet<String> inputs; // For inputs
private static final int MAX_SPEED = 150;
private static final int SPEED = 10;
private static final int ASTEROID_SPEED = 150;
private StackPane background;
/*
* Generates a random number between min and max, inclusive.
*/
private float genRandom(int min, int max) {
return (float) Math.floor(Math.random() * (max - min + 1) + min);
}
/*
* Initializes the asteroids
*/
private void initAsteroids() {
this.asteroids = new ArrayList<Entity>();
for (int i = 0; i < NUM_OF_ASTEROIDS; i++) {
Entity asteroid = new Entity(50, 50, ASTEROID_COLOR, EntityType.ASTEROID);
float px = (float) genRandom(200, WIDTH - 50);
float py = (float) genRandom(200, HEIGHT - 50);
asteroid.setPos(px, py);
// Keep recalculating position until there are no collisions
while (asteroid.intersectsWith(this.asteroids)) {
px = (float) genRandom(200, WIDTH - 50);
py = (float) genRandom(200, HEIGHT - 50);
asteroid.setPos(px, py);
}
// Randomly generate numbers to change velocity by
float dx = this.genRandom(-ASTEROID_SPEED, ASTEROID_SPEED);
float dy = this.genRandom(-ASTEROID_SPEED, ASTEROID_SPEED);
asteroid.changeVelocity(dx, dy);
this.asteroids.add(asteroid);
}
}
/*
* Initializes the player
*/
private void initPlayer() {
this.player = new Player(30, 30, PLAYER_COLOR, EntityType.PLAYER);
this.player.setPos(WIDTH / 2, 50);
}
/*
* Checks collisions with screen boundaries
*/
private void checkOffScreenCollisions(Entity e) {
if (e.getX() < -50)
e.setX(WIDTH);
if (e.getX() > WIDTH)
e.setX(0);
if (e.getY() < -50)
e.setY(HEIGHT);
if (e.getY() > HEIGHT)
e.setY(0);
}
/*
* Controls speed
*/
private void controlSpeed(Entity e) {
if (e.getDx() < -MAX_SPEED)
e.setDx(-MAX_SPEED);
if (e.getDx() > MAX_SPEED)
e.setDx(MAX_SPEED);
if (e.getDy() < -MAX_SPEED)
e.setDy(-MAX_SPEED);
if (e.getDy() > MAX_SPEED)
e.setDy(MAX_SPEED);
}
/*
* Controls each asteroid's speed and collision off screen
*/
private void controlAsteroids(ArrayList<Entity> asteroids) {
for (Entity asteroid : asteroids) {
this.checkOffScreenCollisions(asteroid);
this.controlSpeed(asteroid);
}
}
/*
* Checks an asteroid's collision with another asteroid
*/
private void checkAsteroidCollisions() {
for (int i = 0; i < NUM_OF_ASTEROIDS; i++) {
Entity asteroid = this.asteroids.get(i);
if (asteroid.intersectsWith(this.asteroids)){
float dx = (float) asteroid.getDx();
float dy = (float) asteroid.getDy();
asteroid.setDx(0);
asteroid.setDy(0);
asteroid.changeVelocity(-dx, -dy);
}
}
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Hello World!");
this.initAsteroids();
this.initPlayer();
background = new StackPane();
background.setStyle("-fx-background-color: pink");
this.inputs = new HashSet<String>();
Group root = new Group();
Scene scene = new Scene(root);
primaryStage.setScene(scene);
scene.setOnKeyPressed(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent e) {
String code = e.getCode().toString();
inputs.add(code);
}
});
scene.setOnKeyReleased(new EventHandler<KeyEvent>() {
#Override
public void handle(KeyEvent e) {
String code = e.getCode().toString();
inputs.remove(code);
}
});
Canvas canvas = new Canvas(WIDTH, HEIGHT);
GraphicsContext gc = canvas.getGraphicsContext2D();
background.getChildren().add(canvas);
root.getChildren().add(background);
lastNanoTime = System.nanoTime();
new AnimationTimer() {
#Override
public void handle(long currentNanoTime) {
float elapsedTime = (float) ((currentNanoTime - lastNanoTime) / 1000000000.0);
lastNanoTime = currentNanoTime;
/* PLAYER */
// Game Logic
if (inputs.contains("A"))
player.changeVelocity(-SPEED, 0);
if (inputs.contains("D"))
player.changeVelocity(SPEED, 0);
if (inputs.contains("W"))
player.changeVelocity(0, -SPEED);
if (inputs.contains("S"))
player.changeVelocity(0, SPEED);
// Collision with edge of map
checkOffScreenCollisions(player);
// Control speed
controlSpeed(player);
player.update(elapsedTime);
/* ASTEROIDS */
gc.setFill(ASTEROID_COLOR);
for(int i = 0; i < NUM_OF_ASTEROIDS; i++) {
checkAsteroidCollisions(i); // BUGGY CODE
}
controlAsteroids(asteroids);
gc.clearRect(0, 0, WIDTH, HEIGHT);
for (Entity asteroid : asteroids) {
asteroid.update(elapsedTime);
asteroid.render(gc);
}
gc.setFill(PLAYER_COLOR);
player.render(gc);
}
}.start();
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
Entity.java:
import java.util.ArrayList;
import javafx.geometry.Rectangle2D;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
public class Entity {
private Color color;
private double x, y, width, height, dx, dy;
private EntityType entityType; // ID of this Entity
public Entity(float width, float height, Color color, EntityType type) {
this.x = this.dx = 0;
this.y = this.dy = 0;
this.width = width;
this.height = height;
this.color = color;
this.entityType = type;
}
/*
* Getters and setters
*/
public Color getColor() {
return color;
}
public void setColor(Color color) {
this.color = color;
}
public double getX() {
return x;
}
public void setX(float x) {
this.x = x;
}
public double getY() {
return y;
}
public void setY(float y) {
this.y = y;
}
public double getDx() {
return dx;
}
public void setDx(float dx) {
this.dx = dx;
}
public double getDy() {
return dy;
}
public void setDy(float dy) {
this.dy = dy;
}
public EntityType getEntityType() {
return entityType;
}
/*
* Adds to dx and dy (velocity)
*/
public void changeVelocity(float dx, float dy) {
this.dx += dx;
this.dy += dy;
}
/*
* Sets position
*/
public void setPos(float x, float y) {
this.setX(x);
this.setY(y);
}
/*
* Gets new position of the Entity based on velocity and time
*/
public void update(float time) {
this.x += this.dx * time;
this.y += this.dy * time;
}
/*
* Used for collisions
*/
public Rectangle2D getBoundary() {
return new Rectangle2D(this.x, this.y, this.width, this.height);
}
/*
* Checks for intersections
*/
public boolean intersectsWith(Entity e) {
return e.getBoundary().intersects(this.getBoundary());
}
/*
* If any of the entities in the passed in ArrayList
* intersects with this, then return true;
*/
public boolean intersectsWith(ArrayList<Entity> entities) {
for(Entity e : entities) {
if(e.getBoundary().intersects(this.getBoundary()))
return true;
}
return false;
}
/*
* Draws the shape
*/
public void render(GraphicsContext gc) {
gc.fillRoundRect(x, y, width, height, 10, 10);
}
#Override
public String toString() {
return "Entity [x=" + x + ", y=" + y + ", width=" + width + ", height=" + height + ", entityType=" + entityType
+ "]";
}
}
I think you will need to change the position of the two colliding asteroids slightly so their state is not on collision anymore.
What happens right now is that you change their movement after the collision but I guess that your algorithm notices that they are still touching, so it will try to change the movement again with the same result as before.
What you need to implement now is a change in the position of the two asteroids so your collision detection won´t act up again immediately after it´s first call.
I hope I could help you.
Related
I have a Canvas.java class where I have a mousemove code to track the mouse position, and have it saved to an array. If I print this code out from this class itself, the mouse position is correctly printed. However, is there any way I can access this array from another class, and have it printed still (eg. I want it to appear as a Vaadin label in MainLayout.java)? It seems that the label is only showing '[]' in place of the coordinates I want printed.
Canvas.java:
package com.vaadin.starter.beveragebuddy.ui.components;
import com.vaadin.flow.component.Component;
import com.vaadin.flow.component.HasSize;
import com.vaadin.flow.component.HasStyle;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.component.html.Label;
import com.vaadin.flow.dom.Element;
import com.vaadin.flow.dom.ElementFactory;
import elemental.json.JsonObject;
import java.util.ArrayList;
/**
* Canvas component that you can draw shapes and images on. It's a Java wrapper
* for the
* <a href="https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API">HTML5
* canvas</a>.
* <p>
* Use {#link #getContext()} to get API for rendering shapes and images on the
* canvas.
* <p>
*/
#Tag("canvas")
#SuppressWarnings("serial")
public class Canvas extends Component implements HasStyle, HasSize {
private CanvasRenderingContext2D context;
private Element element;
private boolean isDrawing = false;
private boolean mouseIsDown = false;
private double endX;
private double endY;
public static ArrayList <BoundingBox> arrayBoxes = new ArrayList<BoundingBox>();
public static ArrayList <MousePosition> mousePosArray = new ArrayList<MousePosition>();
public static ArrayList<BoundingBox> getArrayBoxes() {
return arrayBoxes;
}
public static ArrayList<MousePosition> getMousePosArray() {
return mousePosArray;
}
public static void setMousePosArray(ArrayList<MousePosition> mousePosArray) {
Canvas.mousePosArray = mousePosArray;
}
/**
* Creates a new canvas component with the given size.
* <p>
* Use the API provided by {#link #getContext()} to render graphics on the
* canvas.
* <p>
* The width and height parameters will be used for the canvas' coordinate
* system. They will determine the size of the component in pixels, unless
* you explicitly set the component's size with {#link #setWidth(String)} or
* {#link #setHeight(String)}.
*
* #param width
* the width of the canvas
* #param height
* the height of the canvas
*/
public Canvas(int width, int height) {
context = new CanvasRenderingContext2D(this);
element = getElement();
element.getStyle().set("border", "1px solid");
getElement().setAttribute("width", String.valueOf(width));
getElement().setAttribute("height", String.valueOf(height));
element.addEventListener("mousedown", event -> { // Retrieve Starting Position on MouseDown
Element boundingBoxResult = ElementFactory.createDiv();
element.appendChild(boundingBoxResult);
JsonObject evtData = event.getEventData();
double xBox = evtData.getNumber("event.x");
double yBox = evtData.getNumber("event.y");
boundingBoxResult.setAttribute("data-x", String.format("%f", xBox));
boundingBoxResult.setAttribute("data-y", String.format("%f", yBox));
BoundingBox newBox = new BoundingBox(0, xBox, yBox, 0.0, 0.0);
arrayBoxes.add(newBox);
isDrawing = true;
mouseIsDown=true;
}).addEventData("event.x").addEventData("event.y");
element.addEventListener("mouseup", event -> { // Draw Box on MouseUp
Element boundingBoxResult2 = ElementFactory.createDiv();
element.appendChild(boundingBoxResult2);
JsonObject evtData2 = event.getEventData();
endX = evtData2.getNumber("event.x");
endY = evtData2.getNumber("event.y");
boundingBoxResult2.setAttribute("end-x", String.format("%f", endX));
boundingBoxResult2.setAttribute("end-y", String.format("%f", endY));
double xcoordi = 0;
double ycoordi = 0;
double boxWidth = 0;
double boxHeight = 0;
for (int i = 0; i < arrayBoxes.size(); i++) {
arrayBoxes.get(i).setName(i + 1);
arrayBoxes.get(i).setWidth(endX, arrayBoxes.get(i).xcoordi);
arrayBoxes.get(i).setHeight(endY, arrayBoxes.get(i).ycoordi);
xcoordi = arrayBoxes.get(i).getXcoordi();
ycoordi = arrayBoxes.get(i).getYcoordi();
boxWidth = arrayBoxes.get(i).getWidth();
boxHeight = arrayBoxes.get(i).getHeight();
}
mouseIsDown=false;
context.beginPath();
context.setFillStyle("limegreen");
context.setLineWidth(2);
context.strokeRect(xcoordi, ycoordi, boxWidth, boxHeight);
context.fill();
System.out.println(arrayBoxes.toString());
}).addEventData("event.x").addEventData("event.y");
element.addEventListener("mousemove", event -> { // Retrieve Mouse Position when Moving
JsonObject mousePos = event.getEventData();
double mouseX = mousePos.getNumber("event.x");
double mouseY = mousePos.getNumber("event.y");
MousePosition currentPos = new MousePosition(mouseX, mouseY);
mousePosArray.add(0, currentPos);
setMousePosArray(mousePosArray);
System.out.println(mousePosArray.get(0));
// System.out.println(mousePosArray.get(mousePosArray.size() -1));
}).addEventData("event.x").addEventData("event.y");
}
MainLayout.java:
package com.vaadin.starter.beveragebuddy.backend;
import com.vaadin.flow.component.dependency.HtmlImport;
import com.vaadin.flow.component.html.Div;
import com.vaadin.flow.component.html.H2;
import com.vaadin.flow.component.html.Label;
import com.vaadin.flow.component.html.NativeButton;
import com.vaadin.flow.router.Route;
import com.vaadin.starter.beveragebuddy.ui.components.BoundingBox;
import com.vaadin.starter.beveragebuddy.ui.components.Canvas;
import com.vaadin.starter.beveragebuddy.ui.components.CanvasRenderingContext2D;
import com.vaadin.starter.beveragebuddy.ui.components.MousePosition;
import com.vaadin.flow.component.textfield.TextField;
import java.util.ArrayList;
#HtmlImport("frontend://styles/shared-styles.html")
#Route("")
public class MainLayout extends Div {
private CanvasRenderingContext2D ctx;
private Canvas canvas;
ArrayList<MousePosition> mousePosArray = Canvas.getMousePosArray();
ArrayList<BoundingBox> bb = Canvas.getArrayBoxes();
public MainLayout() {
H2 title = new H2("Annotation UI");
title.addClassName("main-layout__title");
canvas = new Canvas(1580, 700);
ctx = canvas.getContext();
Label label = new Label("Coordinates: " + mousePosArray);
canvas.addComponent(label);
add(label);
}
}
CanvasRenderingContext2D.java:
package com.vaadin.starter.beveragebuddy.ui.components;
import java.io.Serializable;
import java.util.ArrayList;
/**
* The context for rendering shapes and images on a canvas.
* <p>
* This is a Java wrapper for the <a href=
* "https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D">same
* client-side API</a>.
*/
public class CanvasRenderingContext2D {
private Canvas canvas;
public static ArrayList<BoundingBox> arrayBoxes = new ArrayList<BoundingBox>();
protected CanvasRenderingContext2D(Canvas canvas) {
this.canvas = canvas;
}
public void setFillStyle(String fillStyle) {
setProperty("fillStyle", fillStyle);
}
public void setStrokeStyle(String strokeStyle) {
setProperty("fillStyle", strokeStyle);
}
public void setLineWidth(double lineWidth) {
setProperty("lineWidth", lineWidth);
}
public void setFont(String font) {
setProperty("font", font);
}
public void arc(double x, double y, double radius, double startAngle,
double endAngle, boolean antiClockwise) {
callJsMethod("arc", x, y, radius, startAngle, endAngle, antiClockwise);
}
public void beginPath() {
callJsMethod("beginPath");
}
public void clearRect(double x, double y, double width, double height) {
callJsMethod("clearRect", x, y, width, height);
Canvas.arrayBoxes.clear();
}
public void closePath() {
callJsMethod("closePath");
}
/**
* Fetches the image from the given location and draws it on the canvas.
* <p>
* <b>NOTE:</b> The drawing will happen asynchronously after the browser has
* received the image.
*
* #param src
* the url of the image to draw
* #param x
* the x-coordinate of the top-left corner of the image
* #param y
* the y-coordinate of the top-left corner of the image
*/
public void drawImage(String src, double x, double y) {
runScript(String.format(
"var zwKqdZ = new Image();" + "zwKqdZ.onload = function () {"
+ "$0.getContext('2d').drawImage(zwKqdZ, %s, %s);};"
+ "zwKqdZ.src='%s';",
x, y, src));
}
/**
* Fetches the image from the given location and draws it on the canvas.
* <p>
* <b>NOTE:</b> The drawing will happen asynchronously after the browser has
* received the image.
*
* #param src
* the url of the image to draw
* #param x
* the x-coordinate of the top-left corner of the image
* #param y
* the y-coordinate of the top-left corner of the image
* #param width
* the width for the image
* #param height
* the height for the image
*/
public void drawImage(String src, double x, double y, double width,
double height) {
runScript(String.format("var zwKqdZ = new Image();"
+ "zwKqdZ.onload = function () {"
+ "$0.getContext('2d').drawImage(zwKqdZ, %s, %s, %s, %s);};"
+ "zwKqdZ.src='%s';", x, y, width, height, src));
}
public void fill() {
callJsMethod("fill");
}
public void fillRect(double x, double y, double width, double height) {
callJsMethod("fillRect", x, y, width, height);
}
public void fillText(String text, double x, double y) {
callJsMethod("fillText", text, x, y);
}
public void lineTo(double x, double y) {
callJsMethod("lineTo", x, y);
}
public void moveTo(double x, double y) {
callJsMethod("moveTo", x, y);
}
public void rect(double x, double y, double width, double height) {
callJsMethod("rect", x, y, width, height);
}
public void restore() {
callJsMethod("restore");
}
public void rotate(double angle) {
callJsMethod("rotate", angle);
}
public void save() {
callJsMethod("save");
}
public void scale(double x, double y) {
callJsMethod("scale", x, y);
}
public void stroke() {
callJsMethod("stroke");
}
public void strokeRect(double x, double y, double width, double height) {
callJsMethod("strokeRect", x, y, width, height);
}
public void strokeText(String text, double x, double y) {
callJsMethod("strokeText", text, x, y);
}
public void translate(double x, double y) {
callJsMethod("translate", x, y);
}
protected void setProperty(String propertyName, Serializable value) {
runScript(String.format("$0.getContext('2d').%s='%s'", propertyName,
value));
}
/**
* Runs the given js so that the execution order works with callJsMethod().
* Any $0 in the script will refer to the canvas element.
*/
private void runScript(String script) {
canvas.getElement().getNode().runWhenAttached(
// This structure is needed to make the execution order work
// with Element.callFunction() which is used in callJsMethod()
ui -> ui.getInternals().getStateTree().beforeClientResponse(
canvas.getElement().getNode(),
context -> ui.getPage().executeJavaScript(script,
canvas.getElement())));
}
protected void callJsMethod(String methodName, Serializable... parameters) {
canvas.getElement().callFunction("getContext('2d')." + methodName,
parameters);
}
}
BoundingBox.java:
package com.vaadin.starter.beveragebuddy.ui.components;
public class BoundingBox {
public double xcoordi = 0;
public double ycoordi = 0;
public double boxWidth = 0;
public double boxHeight = 0;
public int name;
public BoundingBox(int name, double xcoordi, double ycoordi, double boxWidth, double boxHeight) {
this.name = name;
this.xcoordi = xcoordi;
this.ycoordi = ycoordi;
this.boxWidth = boxWidth;
this.boxHeight = boxHeight;
}
public int getName() {
return name;
}
public void setName(int name) {
this.name = name;
}
public double getXcoordi() {
return xcoordi;
}
public void setXcoordi(double xcoordi) {
this.xcoordi = xcoordi;
}
public double getYcoordi() {
return ycoordi;
}
public void setYcoordi(double ycoordi) {
this.ycoordi = ycoordi;
}
public double getWidth() {
return boxWidth;
}
public void setWidth(double endX, double xcoordi) {
boxWidth = endX - xcoordi;
}
public double getHeight() {
return boxHeight;
}
public void setHeight(double endY, double ycoordi) {
boxHeight = endY - ycoordi;
}
#Override
public String toString() {
return "{" +
"Box=" + name +
", X=" + xcoordi +
", Y=" + ycoordi +
", Width=" + boxWidth +
", Height=" + boxHeight +
'}';
}
}
MousePosition.java:
package com.vaadin.starter.beveragebuddy.ui.components;
public class MousePosition {
public double mouseX;
public double mouseY;
public MousePosition(double mouseX, double mouseY) {
this.mouseX = mouseX;
this.mouseY = mouseY;
}
public double getMouseX() {
return mouseX;
}
public void setMouseX(double mouseX) {
this.mouseX = mouseX;
}
public double getMouseY() {
return mouseY;
}
public void setMouseY(double mouseY) {
this.mouseY = mouseY;
}
#Override
public String toString() {
return "MousePosition{" +
"mouseX=" + mouseX +
", mouseY=" + mouseY +
'}';
}
}
Any help is much appreciated, thank you!
Alright, with the updated code it seems your problem is that you never update the label.
You set the label text initially, but it will not be automatically updated when the array changes. What you can do is allow adding a listener to the canvas, and notify it when the value changes.
In the simplest case, a listener can be just a Java Runnable (or Supplier if you want to pass a value). My example returns a Vaadin Registration, which can be used to remove the listener by calling registration.remove().
In Canvas.java
private List<Runnable> mouseMoveListeners = new ArrayList<>(0);
...
public Registration addMouseMoveListener(Runnable listener) {
mouseMoveListeners.add(listener);
return () -> mouseMoveListeners.remove(listener);
}
And in your current mousemove listener
// Your current code in the mousemove listener
MousePosition currentPos = new MousePosition(mouseX, mouseY);
mousePosArray.add(0, currentPos);
setMousePosArray(mousePosArray);
System.out.println(mousePosArray.get(0));
// Add this to run all the listeners that have been added to the canvas
mouseMoveListeners.forEach(Runnable::run);
Then at the end of your MainLayout constructor, something like this
canvas.addMouseMoveListener(() -> label.setValue("Coordinates: " + mousePosArray));
This is untested code, but an approach similar to this should work. The point being that you somehow must notify the MainLayout when the array has changed.
I have below code, I need to alter it so that the balls are generated with a mouse click rather than all of them generating at once. I know I need to use a mouse listener but I do not know how I can integrate that into what I have without "breaking" the app.
No changes needed
import javax.swing.JFrame;
import java.awt.Canvas;
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferStrategy;
public class Display {
public final int width;
public final int height;
private JFrame frame;
private boolean closeRequested;
private long lastFrameTime;
private BufferStrategy bufferStrategy;
private Graphics2D graphics;
public Display(int width, int height){
this.width = width;
this.height = height;
initialize();
}
private void initialize() {
frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.setAutoRequestFocus(true);
frame.setResizable(false);
frame.addWindowListener(new WindowAdapter(){
#Override
public void windowClosing(WindowEvent e){
closeRequested = true;
}
});
Canvas canvas = new Canvas();
canvas.setIgnoreRepaint(true);
canvas.setPreferredSize(new Dimension(width, height));
frame.getContentPane().add(canvas);
frame.setVisible(true);
frame.pack();
frame.setLocationRelativeTo(null);
canvas.createBufferStrategy(2);
bufferStrategy = canvas.getBufferStrategy();
graphics = (Graphics2D) bufferStrategy.getDrawGraphics();
lastFrameTime = System.currentTimeMillis();
}
public boolean isCloseRequested(){
return closeRequested;
}
public void destroy() {
frame.dispose();
}
public void update(){
if (bufferStrategy.contentsLost()){
graphics.dispose();
graphics = (Graphics2D) bufferStrategy.getDrawGraphics();
}
bufferStrategy.show();
}
public Graphics2D getGraphics() {
return graphics;
}
public void sync(int fps) {
if (fps < 1){
return;
}
long currentFrameTime = System.currentTimeMillis();
long deltaTime = currentFrameTime - lastFrameTime;
long timeToSleep = (1000/fps) - deltaTime;
while (System.currentTimeMillis() - currentFrameTime < timeToSleep){
try{
Thread.sleep(1L);
} catch (InterruptedException e) {
}
}
lastFrameTime = System.currentTimeMillis();
}
}
No changes needed
import java.awt.Color;
public class Ball {
public float x;
public float y;
public float sX;
public float sY;
public int radius;
public Color color;
public Ball(float x, float y, float sX, float sY, int radius, Color color){
this.x = x;
this.y = y;
this.sX = sX;
this.sY = sY;
this.radius = radius;
this.color = color;
}
}
This is where I think the mouse listener would be added in for generating the new balls.
How to change addBalls() logic to generate ball with mouse click?
import java.awt.Color;
import java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.Random;
public class BouncingBallsApp {
private Display display;
private ArrayList<Ball> balls = new ArrayList<>();
public BouncingBallsApp() {
display = new Display(800,600);
addBalls();
mainLoop();
display.destroy();
}
private void mainLoop() {
while (!display.isCloseRequested()){
updatePhysics();
draw(display.getGraphics());
display.update();
display.sync(60);
}
}
//Question????How to change this logic to generate ball with mouse click
private void addBalls() {
int numberOfBalls = 20;
Random random = new Random();
for (int i = 0; i < numberOfBalls; i++){
int radius = random.nextInt(40) + 10;
int x = random.nextInt(display.width - radius * 2) + radius;
int y = random.nextInt(display.height - radius * 2) + radius;
float sX = random.nextFloat() * 10f + 3f;
float sY = random.nextFloat() * 10f + 3f;
Color color;
switch (random.nextInt(4)){
case 0:
color = Color.red;
break;
case 1:
color = Color.green;
break;
case 2:
color = Color.yellow;
break;
default:
color = Color.blue;
break;
}
Ball ball = new Ball(x, y, sX, sY, radius, color);
balls.add(ball);
}
}
private void updatePhysics() {
for (Ball ball : balls){
ball.x += ball.sX;
ball.y += ball.sY;
if (ball.x - ball.radius < 0){
ball.sX = Math.abs(ball.sX);
} else if (ball.x + ball.radius > display.width){
ball.sX = -Math.abs(ball.sX);
}
if (ball.y - ball.radius < 0){
ball.sY = Math.abs(ball.sY);
} else if (ball.y + ball.radius > display.height){
ball.sY = -Math.abs(ball.sY);
}
}
}
private void draw(Graphics2D g) {
g.setBackground(Color.black);
g.clearRect(0,0, display.width, display.height);
for (Ball ball : balls){
g.setColor(ball.color);
int x = (int) (ball.x - ball.radius);
int y = (int) (ball.y - ball.radius);
int size = ball.radius * 2;
g.fillOval(x, y, size, size);
}
}
public static void main(String[] args) {
new BouncingBallsApp();
}
}
In BouncingBallsApp constructor do the following changes:
public BouncingBallsApp() {
display = new Display(800,600);
//instead of calling add balls directly, use a mouse listener
//addBalls();
display.addMouseListener(getListener());
mainLoop();
display.destroy();
}
Add getListener() method to BouncingBallsApp:
private MouseListener getListener() {
return new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
addBalls(1); //call add balls when mouse pressed
}
};
}
And slightly change addBalls() so that numberOfBalls becomes an argument:
private void addBalls(int numberOfBalls) {
//int numberOfBalls = 20;
.....
Add mouse listener support to Display:
//add mouse listener to canvas
void addMouseListener(MouseListener listener) {
canvas.addMouseListener(listener); //requiers to make canvas a field
}
All done.
To generate balls, simply click the canvas.
(A link to the full code (you can run it online). )
This question already has answers here:
Create a Trailing line of blood behind a player
(2 answers)
Closed 6 years ago.
I have a program that takes information on two planets/bodies in space and animates their orbits around each other realistically. It works, though when I repaint, it clears the screen each time and does not leave a trail.
My problem is that I want to leave a trail, though any answer I can find online only explains how to get rid of a trail. However, I don't have that problem.
On other computers, I can leave out the super.paintComponent() in my paint method and that causes it to leave a trail, but on this computer, it doesn't do that, it seems to clear the screen automatically. So then how can I efficiently draw a trail behind my orbiting planets? My code follows.
JPanel class first:
import javax.swing.*;
import java.awt.*;
/**
* Created by chris on 3/2/16.
*/
public class SpacePanel2 extends JPanel{
private Body2[] planets;
public static final Dimension SCREENSIZE = Toolkit.getDefaultToolkit().getScreenSize();
public static double scale = 5e6; //m/p
public static Color[] colors = {Color.black, Color.red};
public SpacePanel2(Body2[] planets) {
this.planets = planets;
this.setPreferredSize(SCREENSIZE);
}
#Override
public void paint(Graphics g){
for (int i = 0; i < planets.length; i++) {
g.setColor(colors[i]);
int r = planets[i].getPixelRadius()/2;
int x = planets[i].getPixelX();
int y = planets[i].getPixelY();
g.fillOval(x, y, r, r);
}
}
}
Body class:
/**
* Created by chris on 3/2/16.
*/
public class Body2 {
private double mass; //in kilograms
private double radius; //in meters
private static final double GRAVITATIONAL_CONSTANT = 6.67408e-11;
private static final double AVERAGE_DENSITY = 5515; //kg/m^3
/**
* Movement variables
*/
private double dx; //in m/s
private double dy; //in m/s
private double x; //in m
private double y; //in m
public Body2() {
radius = 1;
mass = AVERAGE_DENSITY;
x = 0;
y = 0;
dx = 0;
dy = 0;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
public double getMass() {
return mass;
}
public double getRadius() {
return radius;
}
public int getPixelX() {
return (int)((this.x-radius)/SpacePanel2.scale);
}
public int getPixelY() {
return (int)((this.y-radius)/SpacePanel2.scale);
}
public int getPixelRadius(){
return (int)(this.radius/SpacePanel2.scale);
}
public void setMass(double mass) {
this.mass = mass;
}
public void setRadius(double radius) {
this.radius = radius;
}
public void setDx(double dx) {
this.dx = dx;
}
public void setDy(double dy) {
this.dy = dy;
}
public void setX(double x) {
this.x = x;
}
public void setY(double y) {
this.y = y;
}
public void exertForce2(double diffY, double diffX, double F){
double dist = Math.sqrt(diffY*diffY + diffX*diffX);
double ratio = F / dist;
this.dy = this.dy + ratio*diffY/this.mass;
this.dx = this.dx + ratio*diffX/this.mass;
}
public void tick(double timeScale) {
x+=(dx/1000.0)*timeScale;
y+=(dy/1000.0)*timeScale;
}
public static double getForce(Body2 a, Body2 b){
double dX = a.getX() - b.getX();
double dY = a.getY() - b.getY();
double distance = Math.sqrt(Math.pow(dX,2)+Math.pow(dY,2));
return (a.getMass()*b.getMass()*GRAVITATIONAL_CONSTANT)/(distance*distance);
}
public static double getStandardMass(double radius){
return (4.0/3.0)*Math.pow(radius, 3) * Math.PI;
}
public double getDy() {
return dy;
}
public double getDx() {
return dx;
}
public static double predictCentripetalForce(Body2 sun, Body2 planet){
return Math.sqrt(getForce(planet, sun)*(sun.getY()-planet.getY())/planet.mass);
}
}
Main class:
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
/**
* Created by chris on 3/2/16.
*/
public class MainSpace2 {
static JFrame frame;
static SpacePanel2 panel;
static int fps = 60;
static boolean getLarger = false;
static boolean getSmaller = false;
static Dimension size = Toolkit.getDefaultToolkit().getScreenSize();
public static void main(String[] args) {
Body2[] test = new Body2[2];
Body2 sun = new Body2();
sun.setRadius(696300000);
sun.setMass(1.989e30);
sun.setX(getScope('x') / 2);
sun.setY(getScope('y') / 2);
sun.setDx(0);
sun.setDy(0);
test[0] = sun;
int literalSizeSun = (int)(sun.getRadius()/SpacePanel2.scale);
Body2 mercury = new Body2();
mercury.setRadius(24400000);
mercury.setMass(Body2.getStandardMass(mercury.getRadius()));
mercury.setDx(Body2.predictCentripetalForce(sun, mercury)*2);
mercury.setDy(0);
mercury.setX(sun.getX());
mercury.setY(sun.getY() + 2 * sun.getRadius());
test[1] = mercury;
int literalSizeMercury = (int)(mercury.getRadius()/SpacePanel2.scale);
frame = new JFrame();
frame.setPreferredSize(size);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
panel = new SpacePanel2(test);
frame.addKeyListener(new KeyListener() {
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e) {
switch (e.getKeyChar()) {
case '-':
getSmaller = true;
getLarger = false;
break;
case '=':
getLarger = true;
getSmaller = false;
break;
}
}
#Override
public void keyReleased(KeyEvent e) {
switch (e.getKeyChar()) {
case '-':
getSmaller = false;
break;
case '=':
getLarger = false;
break;
}
}
});
double timeScale = 60*24;
Timer time = new Timer((int) (1000.0 / fps), new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
double F = Body2.getForce(test[0], test[1]);
double dY = test[1].getY() - test[0].getY();
double dX = test[1].getX() - test[0].getX();
test[0].exertForce2(dY, dX, F);
test[1].exertForce2(-dY, -dX, F);
for (int j = 0; j < test.length; j++) {
test[j].tick(timeScale);
}
panel.repaint(sun.getPixelX(), sun.getPixelY(), literalSizeSun, literalSizeSun);
panel.repaint(mercury.getPixelX(), mercury.getPixelY(), literalSizeMercury, literalSizeMercury);
}
});
frame.add(panel);
frame.pack();
frame.setVisible(true);
time.start();
}
public static double getScope(char k) {
switch (k) {
case 'x':
return size.width * SpacePanel2.scale;
case 'y':
return size.height * SpacePanel2.scale;
default:
return 0;
}
}
}
Custom painting is done by overriding the paintComponent() method, not paint().
if you leave a continuous trail, once you do a 360 rotation you won't see any more animation, so I would think you need to clear the screen eventually.
If you want to leave a trail you can keep an ArrayList of Objects you want to paint. Then in the paintComponent() method you can iterate through the List. This will allow you to add/remove Object from the list so you can control the number of Objects you want to paint each animation.
Check out the DrawOnComponent example from Custom Painting Approaches for an example of this approach. So your animation logic would basically add a new Object (and potentially remove one once you reach a certain limit?) to the List. Then you just invoke repaint() on the panel and all the Objects will be painted.
You already have List to paint each planet. So each animation you would need to add the new location of each planet to the List.
Or, the link shows how you can paint to a BufferedImage. But this approach doesn't allow you to remove a painting once it is done. So it depends an your exact requirement which approach you use.
I am wring the bouncing ball program in java. And I Now have one bouncing ball, I would like to have at least five bouncing balls. I have tried a few ways to do it, however, I only end up with one ball or error.
Do you have any suggestions on how to proceed? This in the piece of code used for the one ball, is it possible to rewrite this piece of code to get multiple balls in a neat way?
import javafx.scene.shape.Rectangle;
public class World {
private final double width, height;
private Ball[] balls;
private final Rectangle pad;
public World(double width, double height) {
this.width = width;
this.height = height;
balls = new Ball[1];
balls[0] = new Ball(10, 10);
balls[0].setVelocity(75.0, 100.0);
pad = new Rectangle(width / 2, 0.9 * height,
width / 8, height / 32);
}
public void move(long elapsedTimeNs) {
balls[0].move(elapsedTimeNs);
constrainBall(balls[0]);
checkForCollisionWithPad(balls[0]);
}
public Ball[] getBalls() {
return (Ball[]) balls.clone();
}
public Rectangle getPad() {
return pad;
}
public void setPadX(double x) {
if (x > width) {
x = width;
}
if (x < 0) {
x = 0;
}
pad.setX(x);
}
private void constrainBall(Ball ball) {
double x = ball.getX(), y = ball.getY();
double dx = ball.getDx(), dy = ball.getDy();
double radius = ball.getRadius();
if (x < radius) {
dx = Math.abs(dx);
} else if (x > width - radius) {
dx = -Math.abs(dx);
}
if (y < radius) {
dy = Math.abs(dy);
} else if (y > height - radius) {
dy = -Math.abs(dy);
}
ball.setVelocity(dx, dy);
}
private void checkForCollisionWithPad(Ball ball) {
if (ball.intersectsArea(
pad.getX(), pad.getY(), pad.getWidth(), pad.getHeight())) {
double dx = ball.getDx();
// set dy negative, i.e. moving "up"
double newDy = -Math.abs(ball.getDy());
ball.setVelocity(dx, newDy);
}
}
}
Main
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import static javafx.application.Application.launch;
import javafx.event.EventHandler;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Alert;
import javafx.scene.input.MouseEvent;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class Bounce extends Application {
private World world;
private Canvas canvas;
private AnimationTimer timer;
protected class BounceTimer extends AnimationTimer {
private long previousNs = 0;
#Override
public void handle(long nowNs) {
if (previousNs == 0) {
previousNs = nowNs;
}
world.move(nowNs - previousNs);
previousNs = nowNs;
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.setFill(Color.WHITESMOKE);
gc.fillRect(0, 0, canvas.getWidth(), canvas.getHeight());
Rectangle pad = world.getPad();
gc.setFill(Color.BLACK);
double x = pad.getX(), y = pad.getY(),
w = pad.getWidth(), h = pad.getHeight();
gc.fillRoundRect(x, y, w, h, h, h);
for (Ball b : world.getBalls()) {
b.paint(gc);
}
}
}
#Override
public void start(Stage stage) {
Group root = new Group();
Scene scene = new Scene(root, 300, 300, Color.WHITESMOKE);
canvas = new Canvas(scene.getWidth(), scene.getHeight());
root.getChildren().add(canvas);
stage.setTitle("Bounce");
stage.setScene(scene);
stage.setResizable(false);
stage.sizeToScene();
stage.show();
world = new World(canvas.getWidth(), canvas.getHeight());
timer = new BounceTimer();
timer.start();
canvas.addEventHandler(MouseEvent.MOUSE_DRAGGED,
new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent me) {
world.setPadX(me.getX());
}
});
}
public static void main(String[] args) {
launch(args);
}
private void showAlert(String message) {
alert.setHeaderText("");
alert.setTitle("Alert!");
alert.setContentText(message);
alert.show();
}
private final Alert alert = new Alert(Alert.AlertType.INFORMATION);
}
Ball
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
public class Ball {
public static final double BILLION = 1_000_000_000.0;
private double x, y; // position of the balls center
private double dx, dy; // velocity measured in pixels/second
private double radius;
private Color color;
public Ball(double x0, double y0) {
x = x0;
y = y0;
radius = 10;
color = Color.MAGENTA;
}
public Ball(double x0, double y0, double rad, Color col) {
x = x0;
y = y0;
radius = rad;
color = col;
}
Ball(int i, int i0, Color BLUEVIOLET) {
throw new UnsupportedOperationException("Not supported yet.");
}
public void setColor(Color col) { // setColor
color = col; }
public double getX() {
return x;
}
public double getY() {
return y;
}
public void setX(double newX) {
x = newX;
}
public void setY(double newY) {
y = newY;
}
public double getRadius() {
return radius;
}
public double getDx() {
return dx;
}
public double getDy() {
return dy;
}
public void setVelocity(double newDx, double newDy) {
dx = newDx;
dy = newDy;
}
public void moveTo(double newX, double newY) {
x = newX;
y = newY;
}
public void move(long elapsedTimeNs) {
x += dx * elapsedTimeNs / BILLION;
y += dy * elapsedTimeNs / BILLION;
}
public void paint(GraphicsContext gc) {
gc.setFill(color);
// arguments to fillOval: see the javadoc for GraphicsContext
gc.fillOval(x - radius, y - radius, radius * 2, radius * 2);
}
public boolean intersectsArea(
double rectX, double rectY,
double rectWidth, double rectHeight) {
double closestX = clamp(x, rectX, rectX + rectWidth);
double closestY = clamp(y, rectY, rectY + rectHeight);
double distanceX = x - closestX;
double distanceY = y - closestY;
return (distanceX * distanceX) + (distanceY * distanceY)
< (radius * radius);
}
private double clamp(double value, double lower, double upper) {
if (value < lower) {
return lower;
}
if (value > upper) {
return upper;
}
return value;
}
}
As Stormblessed said, you are only targeting one ball in your move method.
You should do:
public void move(Ball ball, long elapsedTimeNs) {
ball.move(elapsedTimeNs);
constrainBall(ball);
checkForCollisionWithPad(ball);
}
Edit: Since you want the handler method to accept only the elapsedTimeNs argument, do:
public void move(long elapsedTimeNs) {
for (Ball ball : balls) {
ball.move(elapsedTimeNs);
constrainBall(ball);
checkForCollisionWithPad(ball);
}
}
Edit 2: You should probably have a method that creates a new ball, for convenience:
public Ball newBall(double x, double y, double velocity1, double velocity2) {
Ball tmp = new Ball(x, y);
tmp.setVelocity(velocity1, velocity2);
balls.add(tmp);
return tmp;
}
Edit 3: The reason it throws an error is that you designated balls to have only one index position by using balls = new Ball[1]. You should use an ArrayList (java.util.ArrayList) instead, like so:
import java.util.ArrayList;
ArrayList<Ball> balls = new ArrayList<>;
You should now use balls.add and balls.get instead of = and []. References have been updated accordingly.
I am making a game in java and I want to create a simulation of a cloud that is pouring rain. The cloud is supposed to move to the right while raining. Moving the cloud is no problem. It's the rain that I am struggling with.
What I was thinking of doing was with a timer to draw a rectangle, thats supposed to look like falling rain at a random x value inside of the cloud. And then add 1 to the y value of the drop each 100 millisecond. But I don't want to create 100 different rectangles, x variables and y variables for each rain drop.
Any idea how I can accomplish this? Suggestions appreciated!
It is a 2d game.. Sorry.
One approach would be to consider a marquee on a theater. You take a series of bulbs and, by lighting and extinguishing them in sequence, you can simulate linear motion.
In the same way, rather than creating raindrops and animating their movement, why not creating multiple raindrops that are invisible and show and hide them in sequence to simulate downward motion. Then, you would have a series of arrays representing a raindrop track and you simply need to cycle through then, hiding the current one, incrementing the array pointer and displaying that one.
Is it a requirement that the rain drops be programmed? Traditionally, this would be done with a few rain sprites that you place under the cloud and animate so that it looks like the rain is falling.
I would recommend just storing the values as an ArrayList of objects.
class Raindrop {
private int x;
private int y;
public void fall() {
y--;
}
}
Then make an ArrayList with a generic type.
ArrayList<Raindrop> drops = new ArrayList<Raindrop>();
To make each drop fall,
for (int i=0; i<drops.length(); i++) {
drops.get(i).fall();
}
Here is my java (swing) implementation of 2d rain with drops, splash, wind and gravity
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Main {
public static void main( String [] args ) {
JFrame frame = new JFrame();
frame.setSize(800, 300);
final RPanel rPanel=new RPanel();
frame.add(rPanel);
frame.setVisible( true );
frame.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent e) {
super.windowClosing(e);
rPanel.stop();
System.exit(0);
}
});
}
}
class RPanel extends JPanel {
//*********SETTINGS****************************
private float mWind = 2.05f;
private float mGravity = 9.8f;
private double mRainChance = 0.99; // from 0 to 1
private int mRepaintTimeMS = 16;
private float mRainWidth=1;
private double mDdropInitialVelocity = 20;
private double mDropDiam = 2;
private Color mColor=new Color(0, 0, 255);
//*********************************************
private ArrayList<Rain> rainV;
private ArrayList<Drop> dropV;
private UpdateThread mUpdateThread;
public RPanel() {
rainV = new ArrayList<>();
dropV = new ArrayList<>();
mUpdateThread=new UpdateThread();
mUpdateThread.start();
}
public void stop() {
mUpdateThread.stopped=true;
}
public int getHeight() {
return this.getSize().height;
}
public int getWidth() {
return this.getSize().width;
}
private class UpdateThread extends Thread {
public volatile boolean stopped=false;
#Override
public void run() {
while (!stopped) {
RPanel.this.repaint();
try {
Thread.sleep(mRepaintTimeMS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setStroke(new BasicStroke(mRainWidth));
g2.setColor(mColor);
//DRAW DROPS
Iterator<Drop> iterator2 = dropV.iterator();
while (iterator2.hasNext()) {
Drop drop = iterator2.next();
drop.update();
drop.draw(g2);
if (drop.y >= getHeight()) {
iterator2.remove();
}
}
//DRAW RAIN
Iterator<Rain> iterator = rainV.iterator();
while (iterator.hasNext()) {
Rain rain = iterator.next();
rain.update();
rain.draw(g2);
if (rain.y >= getHeight()) {
//create new drops (2-8)
long dropCount = 1 + Math.round(Math.random() * 4);
for (int i = 0; i < dropCount; i++) {
dropV.add(new Drop(rain.x, getHeight()));
}
iterator.remove();
}
}
//CREATE NEW RAIN
if (Math.random() < mRainChance) {
rainV.add(new Rain());
}
}
//*****************************************
class Rain {
float x;
float y;
float prevX;
float prevY;
public Rain() {
Random r = new Random();
x = r.nextInt(getWidth());
y = 0;
}
public void update() {
prevX = x;
prevY = y;
x += mWind;
y += mGravity;
}
public void draw(Graphics2D g2) {
Line2D line = new Line2D.Double(x, y, prevX, prevY);
g2.draw(line);
}
}
//*****************************************
private class Drop {
double x0;
double y0;
double v0; //initial velocity
double t; //time
double angle;
double x;
double y;
public Drop(double x0, double y0) {
super();
this.x0 = x0;
this.y0 = y0;
v0 = mDdropInitialVelocity;
angle = Math.toRadians(Math.round(Math.random() * 180)); //from 0 - 180 degrees
}
private void update() {
// double g=10;
t += mRepaintTimeMS / 100f;
x = x0 + v0 * t * Math.cos(angle);
y = y0 - (v0 * t * Math.sin(angle) - mGravity * t * t / 2);
}
public void draw(Graphics2D g2) {
Ellipse2D.Double circle = new Ellipse2D.Double(x, y, mDropDiam, mDropDiam);
g2.fill(circle);
}
}
}
You can use a particle system or use a vector of raindrops and animate them every X milliseconds. A link to a particle system library: http://code.google.com/p/jops/
example code for vector:
import java.util.Vector;
// In your class
Vector raindrops;
void animate()
{
ListIterator iter = raindrops.listIterator;
while (iter.hasNext()) {
((Raindrop)iter.next()).moveDown();
}
}