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.
Related
I'm trying to draw isometric tiles in Java and implement a tile picking system using the mouse cursor. I draw the tiles using these math formulas I found and adapted to my tile textures which you can find below. Tiles are 64x64px but flat tiles are only 32px height even if I draw them using the 64x64 sprite.
The map is a simple 2d array where my tiles are represented by their id.
Here is the class I use to convert map coordinates to screen coordinates using the toIso() function. I pass my screen coordinates which represent the cursor position on the screen to the toGrid() function to convert them to map coordinates.
public class Utils {
private static int TILE_WIDTH = Tile.TILE_WIDTH;
private static int TILE_HEIGHT = Tile.TILE_HEIGHT;
private static int TILE_WIDTH_HALF = TILE_WIDTH/2;
private static int TILE_HEIGHT_HALF = TILE_HEIGHT/2;
private static int TILE_WIDTH_QUARTER = TILE_WIDTH_HALF/2;
private static int TILE_HEIGHT_QUARTER = TILE_HEIGHT_HALF/2;
public static int[] toIso(int x, int y){
int i = (x - y) * TILE_WIDTH_HALF;
int j = (x + y) * TILE_HEIGHT_QUARTER;
//800 and 100 are temporary offsets I apply to center the map.
i+=800;
j+=100;
return new int[]{i,j};
}
public static int[] toGrid(int x, int y){
//800 and 100 are temporary offsets I apply to center the map.
x-=800;
y-=100;
int i = ( x / ( TILE_WIDTH_HALF ) + y / ( TILE_HEIGHT_QUARTER )) / 2;
int j = ( y / ( TILE_HEIGHT_QUARTER ) - ( x / ( TILE_WIDTH_HALF ))) / 2;
return new int[]{i,j};
}}
I currently render my tiles by using two for loops and converting the map coordinates to screen coordinates using the toIso() function.
public void render(Graphics g){
for(int x = 0;x<width;x++){
for(int y = 0;y<height;y++){
int[] isoCoords = Utils.toIso(x, y);
int fx = isoCoords[0];//
int fy = isoCoords[1];//
if(world[x][y] == 0){
Tile grass = new GrassTile(0);
grass.render(g, grass.getId(), fx, fy);
}else if(world[x][y] == 1){
Tile water = new WaterTile(1);
water.render(g, water.getId(), fx, fy);
}
}
}
}
I get a diamond shape as I wanted rendered on the screen.
I finally update each tick which are the actual mouse coordinates on screen.
int[] coords = Utils.toGrid(mouseManager.getMouseX(), mouseManager.getMouseY());
tileX = coords[0];
tileY = coords[1];
The selected tile is finally rendered:
BufferedImage selectedTexture = Assets.selected;
int[] coordsIsoSelected = Utils.toIso(this.tileX, this.tileY);
g.drawImage(selectedTexture, coordsIsoSelected[0], coordsIsoSelected[1], Tile.TILE_WIDTH, Tile.TILE_HEIGHT, null);
g.drawRect(Utils.toIso(tileX, tileY)[0], Utils.toIso(tileX, tileY)[1]+Tile.TILE_HEIGHT/2, Tile.TILE_WIDTH, Tile.TILE_HEIGHT/2);//I draw a rectangle to visualize what's happening.
Finally, my tile detection isn't working as expected, it isn't fitting the tiles perfectly, however it seems to be in relation with the rectangle I draw. I can't figure out the solution to this problem, I thank you in advance for reading or any advice you could give to me. If you need more precisions, I would be glad to give you more informations.
Here is a video showing what is actually happening: youtu.be/baCVIfJz2Wo
EDIT:
Here is some of my code you could use to run an application like mine. Sorry for this very messy code, but I tried to make it as short as possible without disturbing the behavior of the "game".
You will need to put the sheet provided before into a "textures" folder created into the ressource folder of the project.
The gfx package:
package fr.romainimberti.isometric.gfx;
import java.awt.image.BufferedImage;
public class Assets {
private static final int width = 64, height = 64;
public static BufferedImage grass, water, selected;
public static void init(){
//Temp
SpriteSheet tileSheet = new SpriteSheet(ImageLoader.loadImage("/textures/sheet.png"));
grass = tileSheet.crop(width*2, 0, width, height);
water = tileSheet.crop(width*9, height*5, width, height);
selected = tileSheet.crop(0, height*5, width, height);
//
}
}
package fr.romainimberti.isometric.gfx;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
public class ImageLoader {
public static BufferedImage loadImage(String path){
try {
return ImageIO.read(ImageLoader.class.getResource(path));
} catch (IOException e) {
e.printStackTrace();
System.exit(1);
}
return null;
}
}
package fr.romainimberti.isometric.gfx;
import java.awt.image.BufferedImage;
public class SpriteSheet {
private BufferedImage sheet;
public SpriteSheet(BufferedImage sheet){
this.sheet = sheet;
}
public BufferedImage crop(int x, int y, int width, int height){
return sheet.getSubimage(x, y, width, height);
}
}
The rest of the project:
package fr.romainimberti.isometric;
public class Launcher {
public static void main(String args[]){
System.setProperty("sun.awt.noerasebackground", "true");
Game game = new Game("Isometric", 1280, 720);
game.start();
}
}
package fr.romainimberti.isometric;
import java.awt.Canvas;
import java.awt.Dimension;
import javax.swing.JFrame;
public class Display {
private JFrame frame;
private Canvas canvas;
private String title;
private int width, height;
public Display(String title, int width, int height){
this.title = title;
this.width = width;
this.height = height;
createDisplay();
}
private void createDisplay(){
frame = new JFrame(title);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(true);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
canvas = new Canvas();
canvas.setPreferredSize(new Dimension(width, height));
canvas.setMaximumSize(new Dimension(width, height));
canvas.setMinimumSize(new Dimension(width, height));
canvas.setFocusable(true);
frame.add(canvas);
frame.pack();
}
public Canvas getCanvas(){
return canvas;
}
public JFrame getFrame(){
return frame;
}
}
package fr.romainimberti.isometric;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.util.concurrent.ThreadLocalRandom;
import javax.swing.JFrame;
import fr.romainimberti.isometric.gfx.Assets;
public class Game implements Runnable {
private Display display;
private int width, height;
public JFrame frame;
private boolean running = false;
private Thread thread;
public String title;
private BufferStrategy bs;
private Graphics g;
public int x, y;
public int[][] world;
public static final int TILE_WIDTH = 64;
public static final int TILE_HEIGHT = 64;
public static final int TILE_WIDTH_HALF = 32;
public static final int TILE_HEIGHT_HALF = 32;
public static final int TILE_WIDTH_QUARTER = 16;
public static final int TILE_HEIGHT_QUARTER = 16;
public int xOffset;
//Input
private MouseManager mouseManager;
public Game(String title, int width, int height){
this.width = width;
this.height = height;
this.mouseManager = new MouseManager(this);
this.world = new int[10][10];
}
private void init(){
display = new Display(title, width, height);
display.getFrame().addMouseListener(mouseManager);
display.getFrame().addMouseMotionListener(mouseManager);
display.getCanvas().addMouseListener(mouseManager);
display.getCanvas().addMouseMotionListener(mouseManager);
this.frame = display.getFrame();
Assets.init();
xOffset = frame.getWidth()/2;
//Fill the world
for(int i = 0;i<world.length;i++){
for(int j=0;j<world[0].length;j++){
int r = ThreadLocalRandom.current().nextInt(0,1+1);
if(r == 0)
world[i][j] = 0;
else
world[i][j] = 1;
}
}
}
private void tick(){
mouseManager.tick();
xOffset = frame.getWidth()/2;
}
private void render(){
bs = display.getCanvas().getBufferStrategy();
if(bs == null){
display.getCanvas().createBufferStrategy(3);
return;
}
g = bs.getDrawGraphics();
//Clear Screen
g.clearRect(0, 0, frame.getWidth(), frame.getHeight());
//Draw Here
//World render
for(int x = 0;x<world.length;x++){
for(int y = 0;y<world[0].length;y++){
int[] isoCoords = toIso(x, y);
int fx = isoCoords[0];//
int fy = isoCoords[1];//
if(world[x][y] == 0){
g.drawImage(Assets.grass, fx, fy, null);
}else if(world[x][y] == 1){
g.drawImage(Assets.water, fx, fy, null);
}
}
}
//Selected tile render
int[] coordsIsoSelected = toIso(x, y);
g.drawImage(Assets.selected, coordsIsoSelected[0], coordsIsoSelected[1], TILE_WIDTH, TILE_HEIGHT, null);
//End Drawing
bs.show();
g.dispose();
}
public void run(){
init();
int fps = 120;
double timePerTick = 1000000000 / fps;
double delta = 0;
long now;
long lastTime = System.nanoTime();
while(running){
now = System.nanoTime();
delta += (now - lastTime) / timePerTick;
lastTime = now;
if(delta >= 1){
tick();
render();
delta--;
}
}
stop();
}
public MouseManager getMouseManager(){
return mouseManager;
}
public int getWidth(){
return width;
}
public int getHeight(){
return height;
}
public synchronized void start(){
if(running)
return;
running = true;
thread = new Thread(this);
thread.start();
}
public synchronized void stop(){
if(!running)
return;
running = false;
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static int[] toIso(int x, int y){
int i = (x - y) * TILE_WIDTH_HALF;
int j = (x + y) * TILE_HEIGHT_QUARTER;
i+=xOffset;
return new int[]{i,j};
}
public static int[] toGrid(int x, int y){
x-=xOffset;
int i = ( x / ( TILE_WIDTH_HALF ) + y / ( TILE_HEIGHT_QUARTER )) / 2;
int j = ( y / ( TILE_HEIGHT_QUARTER ) - ( x / ( TILE_WIDTH_HALF ))) / 2;
return new int[]{i,j};
}
}
package fr.romainimberti.isometric;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
public class MouseManager implements MouseListener, MouseMotionListener {
private boolean leftPressed, rightPressed;
private int mouseX, mouseY;
private Game game;
public MouseManager(Game game){
this.game = game;
}
public void tick(){
game.x = game.toGrid(mouseX, mouseY)[0];
game.y = game.toGrid(mouseX, mouseY)[1];
}
// Getters
public boolean isLeftPressed(){
return leftPressed;
}
public boolean isRightPressed(){
return rightPressed;
}
public int getMouseX(){
return mouseX;
}
public int getMouseY(){
return mouseY;
}
// Implemented methods
#Override
public void mousePressed(MouseEvent e) {
if(e.getButton() == MouseEvent.BUTTON1)
leftPressed = true;
else if(e.getButton() == MouseEvent.BUTTON3)
rightPressed = true;
}
#Override
public void mouseReleased(MouseEvent e) {
if(e.getButton() == MouseEvent.BUTTON1)
leftPressed = false;
else if(e.getButton() == MouseEvent.BUTTON3)
rightPressed = false;
}
#Override
public void mouseMoved(MouseEvent e) {
mouseX = e.getX();
mouseY = e.getY();
}
#Override
public void mouseDragged(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseClicked(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseEntered(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseExited(MouseEvent e) {
// TODO Auto-generated method stub
}
}
If you need it, you can find here my project architecture so you can organize all the files correctly.
Again, sorry for this very, very messy code but I had to split all the usefull parts of my game to reduce it's size. Also don't forget to download and place correctly the sheet file. Hope this will help.
128*64 tiles
just wanted to say that I finally solved it. It was just a conversion to int issue. These are the final methods that I use. Hope it will help people who are trying to work with isometric tiles. Thank you !
public static int[] toIso(int x, int y){
int i = (x - y) * TILE_WIDTH_HALF;
int j = (x + y) * TILE_HEIGHT_QUARTER;
i += xOffset-TILE_WIDTH_HALF;
j+=yOffset;
return new int[]{i,j};
}
public static int[] toGrid(double i, double j){
i-=xOffset;
j-=yOffset;
double tx = Math.ceil(((i / TILE_WIDTH_HALF) + (j / TILE_HEIGHT_QUARTER))/2);
double ty = Math.ceil(((j / TILE_HEIGHT_QUARTER) - (i / TILE_WIDTH_HALF))/2);
int x = (int) Math.ceil(tx)-1;
int y = (int) Math.ceil(ty)-1;
return new int[]{x, y};
}
After replacing the spritesheet with the new one with 128x64 pixels' tiles, I've been able to achieve the desired output partially...
Why I say "partially"? Because I've managed to get the desired result only from the half right part of the map.
I believe it could have something to do with how the map is being painted, I'm not a native English speaker so I might be misunderstanding what the "Notes" section says in OP's link:
Notice that the "origin" of the isometric tile is the top corner. But usually when we draw a sprite it's from the top-left corner
I've called the methods toGrid() and toIso() at the beginning of the program as follows:
int[] coordinates = Game.toIso(2, 1);
System.out.println(coordinates[0] + "-" + coordinates[1]);
int[] coordinates2 = Game.toGrid(coordinates[0], coordinates[1]);
System.out.println(coordinates2[0] + "-" + coordinates2[1]);
And got the following results, (Which indeed are what we were expecting), so we know the methods work correctly:
64-96
2-1
I was sure to modify the Assets file:
public static final int WIDTH = 128, HEIGHT = 64;
Where I also changed the variable names following the Java naming conventions (ALL_WORDS_UPPER_CASE_CONSTANTS) and made it public instead of private
I also changed the Game file:
public static final int TILE_WIDTH = Assets.WIDTH;
public static final int TILE_HEIGHT = Assets.HEIGHT;
public static final int TILE_WIDTH_HALF = TILE_WIDTH / 2;
public static final int TILE_HEIGHT_HALF = TILE_HEIGHT / 2;
public static final int TILE_WIDTH_QUARTER = TILE_WIDTH / 4;
public static final int TILE_HEIGHT_QUARTER = TILE_HEIGHT / 4;
To use those constants on the Assets file and calculate the HALF and QUARTER instead of hardcoding it.
I also believe xOffset shouldn't be public but private as well as some other variables on the program...
The tick() method, doesn't need to calculate the xOffset everytime, so we can get rid of this line inside it:
xOffset = frame.getWidth() / 2 - 65;
I also changed the way you paint the tile you're selecting as:
// Selected tile render
int[] coordsIsoSelected = toIso(x, y);
g.drawImage(Assets.selected, coordsIsoSelected[0], coordsIsoSelected[1], TILE_WIDTH, TILE_HEIGHT, null);
And for the Tolso equations, I changed them to:
public static int[] toIso(int x, int y) {
int i = (x - y) * TILE_WIDTH_HALF;
int j = (x + y) * TILE_HEIGHT_HALF;
i += xOffset;
return new int[] { i, j };
}
Below I adjusted the parenthesis locations:
public static int[] toGrid(int x, int y) {
x -= xOffset;
int i = ((x / TILE_WIDTH_HALF) + (y / TILE_HEIGHT_HALF)) / 2;
int j = ((y / TILE_HEIGHT_HALF) - (x / TILE_WIDTH_HALF)) / 2;
return new int[] { i, j };
}
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.
When my animation is in progress figures get stuck together. I think it's because if Figure get velocity x=5 y=5 i move them and then check if they hit anything and my figure can be already inside 2nd figure.
I want to check if they hit anything more often but im not sure how to put my methods in actionPerformed method.
Velocity of figures is not constant.
Do you have any ideas, examples or suggestions?
public class PaintFigures extends JPanel implements ActionListener {
static List<Figure> figuresList = new ArrayList<Figure>();
Timer t = new Timer(5, this);
public PaintFigures(List<Figure> figuresList) {
PaintFigures.figuresList = figuresList;
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
t.start();
for (Figure figure : figuresList) {
figure.drawItself(g2d);
}
}
#Override
public void actionPerformed(ActionEvent e) {
FiguresUpdate.update(figuresList); // Check if they hit anything (other figure or frame)
FiguresUpdate.move(figuresList); // move them
repaint();
}
}
Runnable Example Here
Class main
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Test extends JPanel implements ActionListener {
static private List<Square> figuresList = new ArrayList<Square>();
Timer t = new Timer(5, this);
public static void main(String[] args) {
Square s1 = new Square(40);
Square s2 = new Square(60);
Square s3 = new Square(20);
figuresList.add(s1);
figuresList.add(s3);
figuresList.add(s2);
JFrame frame = new JFrame("Figures Animation");
frame.setSize(700, 400);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
JPanel panel = new Test();
panel.setBackground(Color.GRAY);
frame.getContentPane().add(BorderLayout.CENTER, panel);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
t.start();
for (Square figure : figuresList) {
figure.drawItself(g2d);
}
}
#Override
public void actionPerformed(ActionEvent e) {
Test.update(figuresList); // Check if they bounce
// FiguresUpdate.move(figuresList); // move them
repaint();
}
public static void update(List<Square> list) {
updateFlags(list);
for (int i = 0; i < list.size(); i++) {
list.get(i).setLocationX(
list.get(i).getLocationX() + (list.get(i).getVelocityX()));
list.get(i).setLocationY(
list.get(i).getLocationY() + (list.get(i).getVelocityY()));
if (list.get(i).getLocationX() < 0
|| list.get(i).getLocationX() > 680 - (list.get(i)
.getWidth())) {
WallXBounceDetected(list.get(i));
}
if (list.get(i).getLocationY() < 0
|| list.get(i).getLocationY() > 360 - (list.get(i)
.getHeight())) {
WallYBounceDetected(list.get(i));
}
for (int j = i + 1; j < list.size(); j++) {
if (list.get(i).getBounds().intersects(list.get(j).getBounds())
&& (!list.get(i).getDidHeBounce())
&& (!list.get(j).getDidHeBounce())) {
System.out.println(list.get(i).getClass().getSimpleName());
FigureBounceDetected(list.get(i), list.get(j));
}
}
}
}
public static void updateFlags(List<Square> list) {
for (int i = 0; i < list.size(); i++) {
list.get(i).setDidHeBounce(false);
}
}
public static void WallXBounceDetected(Square f) {
f.setVelocityX(-f.getVelocityX());
}
public static void WallYBounceDetected(Square f) {
f.setVelocityY(-f.getVelocityY());
}
public static void FigureBounceDetected(Square f1, Square f2) {
// Elastic Collision
// Figure 1
double newSpeedF1X = (f1.getVelocityX() * (f1.getMass() - f2.getMass()) + (2 * f2
.getMass() * f2.getVelocityX()))
/ (f1.getMass() + f2.getMass());
double newSpeedF1Y = (f1.getVelocityY() * (f1.getMass() - f2.getMass()) + (2 * f2
.getMass() * f2.getVelocityY()))
/ (f1.getMass() + f2.getMass());
// Figure 2
double newSpeedF2X = (f2.getVelocityX() * (f2.getMass() - f1.getMass()) + (2 * f1
.getMass() * f1.getVelocityX()))
/ (f1.getMass() + f2.getMass());
double newSpeedF2Y = (f2.getVelocityY() * (f2.getMass() - f1.getMass()) + (2 * f1
.getMass() * f1.getVelocityX()))
/ (f1.getMass() + f2.getMass());
f1.setLocationX(f1.getLocationX() + (newSpeedF1X));
f1.setLocationY(f1.getLocationY() + (newSpeedF1Y));
f2.setLocationX(f2.getLocationX() + (newSpeedF2X));
f2.setLocationY(f2.getLocationY() + (newSpeedF2Y));
// new velocity
f1.setVelocityX(newSpeedF1X);
f1.setVelocityY(newSpeedF1Y);
f2.setVelocityX(newSpeedF2X);
f2.setVelocityY(newSpeedF2Y);
// flag true
f1.setDidHeBounce(true);
f2.setDidHeBounce(true);
}
}
Class Square.
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.util.Random;
public class Square {
Rectangle2D.Double square;
private double locationX = 120;
private double locationY = 120;
private double velocityX =1;
private double velocityY =1;
private double width;
private double height = width;
private double mass = width;
private boolean didHeBounce=false;
Color color;
public Square(int width) {
this.width = width;
height = this.width;
mass = height;
Random r = new Random();
if(r.nextInt(2)>0){
velocityX=-1;
} else {
velocityX=1;
}
if(r.nextInt(2)>0){
velocityY=-1;
} else {
velocityY=1;
}
locationX =r.nextInt(540);
locationY= r.nextInt(220);
}
public void drawItself(Graphics g){
Graphics2D g2d = (Graphics2D) g;
square = new Rectangle2D.Double(locationX,locationY,height,width);
g2d.fill(square);
g.setColor(Color.BLUE);
}
public boolean getDidHeBounce() {
return didHeBounce;
}
public void setDidHeBounce(boolean didHeBounce){
this.didHeBounce = didHeBounce;
}
public double getLocationX() {
return locationX;
}
public void setLocationX(double locationX) {
this.locationX = locationX;
}
public double getLocationY() {
return locationY;
}
public void setLocationY(double locationY) {
this.locationY = locationY;
}
public double getVelocityX() {
return velocityX;
}
public void setVelocityX(double velocityX) {
this.velocityX = velocityX;
}
public double getVelocityY() {
return velocityY;
}
public void setVelocityY(double velocityY) {
this.velocityY = velocityY;
}
public double getMass() {
return mass;
}
public double getHeight() {
return height;
}
public double getWidth() {
return width;
}
public Rectangle2D getBounds() {
return square.getBounds2D();
}
}
Hello first of all you moved them here:
f1.setLocationX(f1.getLocationX() + (newSpeedF1X));
f1.setLocationY(f1.getLocationY() + (newSpeedF1Y));
f2.setLocationX(f2.getLocationX() + (newSpeedF2X));
f2.setLocationY(f2.getLocationY() + (newSpeedF2Y));
So it's not frequency problem.
Problem has to be in formula and it is.
You got:
//figure 1
newSpeedF1X = velocityXF1*(MassF1-MassF2)+(2*MassF2*VelocityXF2)/(MassF1+MassF2)
newSpeedF1Y = velocityYF1*(MassF1-MassF2)+(2*MassF2*VelocityXF2)/(MassF1+MassF2)
// Figure 2
newSpeedF2X = velocityXF2*(MassF2-MassF1)+(2*MassF1*VelocityXF1)/(MassF1+MassF2)
newSpeedF2Y = velocityYF2*(MassF2-MassF1)+(2*MassF1*VelocityXF1)/(MassF1+MassF2)
And in newSpeedF2Y should be VelocityYF1 not X
newSpeedF2Y = velocityYF2*(MassF2-MassF1)+(2*MassF1*VelocityYF1)/(MassF1+MassF2)
Additional remarks
Wall bounce detect:
I noticed your figures getting stuck in walls and you change their velocity to -velocity when they are out of bunds so they cant get out.
To avoid figures getting stuck in wall you should do something like this:
public void wallXBounceDetect(Figure f) {
f.setVelocityX(wallBounceDetect(f.getLocationX(), f.getWidth(), canvas.getWidth(), f.getVelocityX()));
}
public void wallYBounceDetect(Figure f) {
f.setVelocityY(wallBounceDetect(f.getLocationY(), f.getHeight(), canvas.getHeight(), f.getVelocityY()));
}
public double wallBounceDetect(double location, double size, double maxValue, double velocity) {
if ((location < 0 && velocity < 0) || (location + size > maxValue && velocity > 0)) {
return -velocity;
}
return velocity;
}
Where canvas is your class with method PaintComponent which extends JPanel.
It has to do with the laws you are applying. If the bouncing is not long enough they will probably stick forever. A simple rule: if the two figures collide and figure 1 is lower than figure 2 (f1.xf2.x) f1 is bounced a bit back otherwise (f1.x>f2.x) it's bounced a bit forward. It seems to work for me right now. You need to check the laws and what values they give (newSpeedF1X etc)
public static void FigureBounceDetected(Square f1, Square f2) {
// Elastic Collision
// Figure 1
double newSpeedF1X = (f1.getVelocityX() * (f1.getMass() - f2.getMass()) + (2 * f2
.getMass() * f2.getVelocityX()))
/ (f1.getMass() + f2.getMass());
double newSpeedF1Y = (f1.getVelocityY() * (f1.getMass() - f2.getMass()) + (2 * f2
.getMass() * f2.getVelocityY()))
/ (f1.getMass() + f2.getMass());
// Figure 2
double newSpeedF2X = (f2.getVelocityX() * (f2.getMass() - f1.getMass()) + (2 * f1
.getMass() * f1.getVelocityX()))
/ (f1.getMass() + f2.getMass());
double newSpeedF2Y = (f2.getVelocityY() * (f2.getMass() - f1.getMass()) + (2 * f1
.getMass() * f1.getVelocityX()))
/ (f1.getMass() + f2.getMass());
System.out.println("prev "+f1.getprevx()+" "+newSpeedF1X+" "+newSpeedF1Y+" "+newSpeedF2X+" "+newSpeedF2Y);
// f1.setLocationX(f1.getLocationX() + (newSpeedF1X));
// f1.setLocationY(f1.getLocationY() + (newSpeedF1Y));
// f2.setLocationX(f2.getLocationX() + (newSpeedF2X));
// f2.setLocationY(f2.getLocationY() + (newSpeedF2Y));
if(f1.getLocationX()<f2.getLocationX()) f1.setLocationX(Math.max(0, f1.getLocationX()-f1.getWidth()));
else f1.setLocationX(Math.min(700-f1.getWidth(), f1.getLocationX()+f1.getWidth()));
// new velocity
// f1.setVelocityX(newSpeedF1X);
// f1.setVelocityY(newSpeedF1Y);
// f2.setVelocityX(newSpeedF2X);
// f2.setVelocityY(newSpeedF2Y);
// flag true
f1.setDidHeBounce(true);
f2.setDidHeBounce(true);
}
}
Some other changes are minor
for (int j = i + 1; j < list.size(); j++) {
int ij=j%list.size();
if (list.get(i).getBounds().intersects(list.get(j).getBounds())
&& (!list.get(i).getDidHeBounce())
&& (!list.get(ij).getDidHeBounce())
) {
System.out.println(list.get(i).getClass().getSimpleName());
FigureBounceDetected(list.get(i), list.get(ij));
}
public void drawItself(Graphics2D g){
Color c=g.getColor();
Graphics2D g2d = (Graphics2D) g;
square = new Rectangle2D.Double(locationX,locationY,height,width);
g.setColor(Color.BLUE);
g.fill(square);
g.setColor(c);
}
I'm having trouble getting my bullets to go where they want and I was hoping I could get some help identifying where my calculations are off.
Basically I'm making a 2d game and where ever you click on the screen a bullet should shoot at that location that you clicked, but the problem is that my bullets are not going where I want them to. Now I believe my problem is somewhere in the checkScreenForTouch() method and how I'm calculating what should be added to the bullets location. I'm not the best at trigonometry but I do understand it, so if any one has any advice on how to fix the following code that would be super cool :).
private void updateBullets(float dt){
batch.begin();
for(int i = 0; i < bullets.size(); i++){
Bullet b = bullets.get(i);
b.velocity.x += b.deltaX * b.speed;
b.velocity.y += b.deltaY * b.speed;
b.position.x += b.velocity.x * dt;
b.position.y += b.velocity.y * dt;
batch.draw(bullTex, b.position.x, b.position.y, bullTex.getWidth(), bullTex.getHeight());
if(b.position.x < 0 || b.position.x > sWidth || b.position.y < 0 || b.position.y > sHeight){
bullets.remove(b);
continue;
}
}
batch.end();
}
private void checkForScreenTouch(float dt){
if(Gdx.input.isTouched()){
Bullet b = new Bullet();
double angle = Math.atan((p.pos.y - Gdx.input.getY())/(p.pos.x - Gdx.input.getX()));
b.deltaX = Math.cos(angle);
b.deltaY = Math.sin(angle);
b.position.x = p.pos.x; //sets bullet start position equal to the players
b.position.y = p.pos.y;
bullets.add(b); // array list of bullets
}
}
If you need anything clarified just let me know. Thanks.
I see you still haven't really gotten any advice on this. Had some time, so I thought I'd whip something up.
The problems you were having I was able to reproduce by using your code.
Bullets only shoot to the right - This was solved by using atan2(dy, dx).
Opposite angle of what they are supposed to be - This was caused because the calculation p.pos.y - Gdx.input.getY() should really have been negated: Gdx.input.getY() - p.pos.y. Just a simple mix up. When the mouse has a larger y coordinate than the player location, you'd want the change in y to be positive.
Notes about the application:
Some issue with frame resizing. Tends to crop out bullets mid-trajectory.
I divide the change in time by 1000 to signify I want the velocity to be of pixels/second. Just a habit thing from physics 20.
The application is in swing. The code you will really want is within the makeProjectile function at the top.
Beyond that, I just used a synchronized list to avoid concurrency issues with the updates and the rendering.
If you have any questions, feel free to ask :)
import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
/**
* #author Obicere
*/
public class ProjectileTest {
// It's dangerous to go alone! Take this.
public void makeProjectile(final MouseEvent e, final int width, final int height){
final int x = width / 2;
final int y = height / 2;
final double angle = Math.atan2(e.getY() - y, e.getX() - x);
final double deltaX = Math.cos(angle);
final double deltaY = Math.sin(angle);
projectiles.add(new Projectile(x, y, deltaX, deltaY));
}
private static final double PROJECTILE_VELOCITY = 100; // Pixels per second
private final Collection<Projectile> projectiles = Collections.synchronizedCollection(new LinkedList<>());
public static void main(final String[] args){
SwingUtilities.invokeLater(ProjectileTest::new);
}
public ProjectileTest(){
final JFrame frame = new JFrame("Projectile Test");
final JPanel content = new JPanel(){
private final Dimension size = new Dimension(500, 500);
#Override
public void paintComponent(final Graphics g){
super.paintComponent(g);
g.setColor(Color.BLACK);
g.drawOval(getWidth() / 2 - 2, getHeight() / 2 - 2, 4, 4);
projectiles.forEach((e) -> e.render(g));
}
#Override
public Dimension getPreferredSize(){
return size;
}
};
content.addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(final MouseEvent e) {
makeProjectile(e, content.getWidth(), content.getHeight());
}
});
content.addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseDragged(final MouseEvent e) {
makeProjectile(e, content.getWidth(), content.getHeight());
}
});
final Timer repaint = new Timer(10, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
final Iterator<Projectile> iter = projectiles.iterator();
while(iter.hasNext()){
final Projectile next = iter.next();
if(!next.valid()){
iter.remove();
}
next.step(content.getWidth(), content.getHeight());
}
frame.repaint();
}
});
frame.add(content);
frame.pack();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
repaint.start();
}
public class Projectile {
private final double velocityX;
private final double velocityY;
private double x;
private double y;
private long lastUpdate;
private boolean valid = true;
public Projectile(final double x, final double y, final double vx, final double vy){
this.x = x;
this.y = y;
this.velocityX = vx;
this.velocityY = vy;
this.lastUpdate = System.currentTimeMillis();
}
public boolean valid(){
return valid;
}
public void destroy(){
this.valid = false;
}
public void step(final int width, final int height){
final long time = System.currentTimeMillis();
final long change = time - lastUpdate;
this.x += (change / 1000D) * (velocityX * PROJECTILE_VELOCITY);
this.y += (change / 1000D) * (velocityY * PROJECTILE_VELOCITY);
this.lastUpdate = time;
if(x < 0 || y < 0 || x > width || y > height){
destroy();
}
}
public void render(final Graphics g){
g.setColor(Color.RED);
g.drawOval((int) x - 2, (int) y - 2, 4, 4);
}
}
}
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();
}
}