As a project, I'm attempting to create an emulation of the game Asteroids. Currently, I'm trying to make it so then the spaceship appears on the opposite side of the GUI if the player ever sends himself out of bounds.
However, I've become quite confused between how AffineTransformation's rotate and createTransformedShape function interacts with the x and y points of the polygon.
Currently, I have the spaceship working; it flies in the angle the user specifies it and all the movement involved works fine. However, when I tried to get the lowest X coordinate of the spaceship to compare if it ever goes bigger than the constant WIDTH, it returns 780. This happens all the time whether or not I am on one side of the map to the other, it always returns 780. This I find really strange because shouldn't it return the smallest X coordinate of where it currently is?
Here is a screenshot of the console displaying that the "smallest x coordinate" of the polygon is 780, despite not being at 780
Can someone explain to me why the X coordinates of the polygon are not changing? I have 2 classes. One, which is the driver classes, and the other which is the ship class which extends Polygon.
public class AsteroidGame implements ActionListener, KeyListener{
public static AsteroidGame game;
public Renderer renderer;
public boolean keyDown = false;
public int playerAngle = 0;
public boolean left = false;
public boolean right = false;
public boolean go = false;
public boolean back = false;
public boolean still = true;
public double angle = 0;
public int turnRight = 5;
public int turnLeft = -5;
public Shape transformed;
public Shape transformedLine;
public Point p1;
public Point p2;
public Point center;
public Point p4;
public final int WIDTH = 1600;
public final int HEIGHT = 800;
public Ship ship;
public AffineTransform transform = new AffineTransform();
public AsteroidGame(){
JFrame jframe = new JFrame();
Timer timer = new Timer(20, this);
renderer = new Renderer();
jframe.add(renderer);
jframe.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jframe.setSize(WIDTH, HEIGHT);
jframe.setVisible(true);
jframe.addKeyListener(this);
jframe.setResizable(false);
int xPoints[] = {800, 780, 800, 820};
int yPoints[] = {400, 460, 440, 460};
p1 = new Point(400,400);
p2 = new Point(380, 460);
center = new Point(400,440);//center
p4 = new Point(420, 460);
ship = new Ship(xPoints, yPoints, 4, 0);
transformed = transform.createTransformedShape(ship);
timer.start();
}
public void repaint(Graphics g){
g.setColor(Color.BLACK);
g.fillRect(0, 0, WIDTH, HEIGHT);
Graphics2D g2d = (Graphics2D)g;
g2d.setColor(Color.WHITE);
g2d.draw(transformed);
/*
g2d.draw(r2);
Path2D.Double path = new Path2D.Double();
path.append(r, false);
AffineTransform t = new AffineTransform();
t.rotate(Math.toRadians(45));
path.transform(t);
g2d.draw(path);
Rectangle test = new Rectangle(WIDTH/2, HEIGHT/2, 200, 100);
Rectangle test2 = new Rectangle(WIDTH/2, HEIGHT/2, 200, 100);
g2d.draw(test2);
AffineTransform at = AffineTransform.getTranslateInstance(100, 100);
g2d.rotate(Math.toRadians(45));
g2d.draw(test);
*/
}
public void actionPerformed(ActionEvent arg0) {
// TODO Auto-generated method stub
if (right){
ship.right();
transform.rotate(Math.toRadians(turnRight), ship.getCenterX(), ship.getCenterY());
System.out.println(ship.getCenterY());
}
else if (left){
ship.left();
transform.rotate(Math.toRadians(turnLeft), ship.getCenterX(), ship.getCenterY());
}
if (go){
ship.go();
//ship.x += Math.sin(Math.toRadians(angle)) * 5;
//ship.y
/*
ship.x += (int) Math.sin(Math.toRadians(angle));
ship.y += (int) Math.cos(Math.toRadians(angle));
*/
//System.out.println(Math.sin(Math.toRadians(ship.angle)) * 5 + "y" + Math.cos(Math.toRadians(ship.angle)) * 5);
}
else if (back){
ship.reverse();
}
ship.move();
//ship.decrement();
transformed = transform.createTransformedShape(ship);
if (ship.smallestX() >= WIDTH){
System.out.println("out");
}
renderer.repaint();
System.out.println("Smallest x coordinate: " + ship.smallestX());
}
public static void main(String[] args){
game = new AsteroidGame();
}
#Override
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
if (e.getKeyCode() == KeyEvent.VK_RIGHT){
System.out.println("I am down");
right = true;
keyDown = true;
}else if (e.getKeyCode() == KeyEvent.VK_LEFT){
left = true;
System.out.println("I am down");
keyDown = true;
}
if (e.getKeyCode() == KeyEvent.VK_UP){
go = true;
}
else if (e.getKeyCode() == KeyEvent.VK_DOWN){
back = true;
}
}
#Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
if (e.getKeyCode() == KeyEvent.VK_RIGHT){
right = false;
}
if (e.getKeyCode() == KeyEvent.VK_LEFT){
left = false;
}
if (e.getKeyCode() == KeyEvent.VK_UP){
go = false;
}
if (e.getKeyCode() == KeyEvent.VK_DOWN){
back = false;
}
still = true;
keyDown = false;
System.out.println("up");
}
#Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
Ship Class:
public class Ship extends Polygon{
/**
*
*/
private double currSpeed = 0;
private static final long serialVersionUID = 1L;
public double angle;
public Ship(int[] x, int[] y, int points, double angle){
super(x, y, points);
this.angle= angle;
}
public void right(){
angle += 5;
}
public void left(){
angle -= 5;
}
public void move(){
for (int i = 0; i < super.ypoints.length; i++){
super.ypoints[i] -= currSpeed;
//System.out.println(super.ypoints[i]);
//System.out.println(super.xpoints[i]);
}
}
public void reverse(){
if (currSpeed > -15) currSpeed -= 0.2;
}
public void go(){
if (currSpeed < 25) currSpeed += 0.5;
}
public int smallestX(){
int min = super.xpoints[0];
for (int i = 0; i < super.xpoints.length; i++){
if (min > super.xpoints[i]){
min = super.xpoints[i];
}
}
return min;
}
public int smallestY(){
int min = super.ypoints[0];
for (int i = 0; i < super.ypoints.length; i++){
if (min < super.ypoints[i]){
min = super.ypoints[i];
}
}
return min;
}
public int getCenterX(){
return super.xpoints[2];
}
public int getCenterY(){
return super.ypoints[2];
}
public double getAng(){
return angle;
}
Why it doesn't work
The affine transformation doesn't actually affect the ship instance, instead it created an entirely new instance, this is why your method doesn't seem to work. To prove this, i have the following code:
int[] xPoints = {800, 780, 800, 820};
int[] yPoints = {400, 460, 440, 460};
Ship ship = new Ship(xPoints, yPoints, 4, 0);
System.out.println("old points:");
System.out.println(Arrays.toString(ship.xpoints));
System.out.println(Arrays.toString(ship.ypoints));
AffineTransform transform = new AffineTransform();
transform.translate(20, 20);
Shape transformed = transform.createTransformedShape(ship);
System.out.println("new points (unchanged):");
System.out.println(Arrays.toString(ship.xpoints));
System.out.println(Arrays.toString(ship.ypoints));
Both times, the points are the same.
Solution
What i suggest you do is instead of thinking of the point as actual points on the screen, think of them as a model, that will also make the physics part easier. We'll center the points around (0,0). And when we need to render it, tranform it to the correct position and rotation.
The points would then be something like this:
int[] xPoints = {0, -20, 0, 20};
int[] yPoints = {-40, 20, 0, 20};
So now, you don't need to edit all the points when you move the ship, only the rotation and the center. Note that you should translate first, and then rotate.
The fact that these point are around (0,0) is important, because this is the center of your rotation. This is probably why you seem to be able to move at the moment, your rotation is not centered around the center of the ship. And when you rotate and move up, you also move a bit to the right.
This solves our problem, because now you don't need to look at all the points anymore, you can just check the center of the ship. If the center goes out of bounds, move it to the other side.
Resulting in the folliwing simple code (i use 300 here because that's the width and height in my example, change it to your width and height in the actual code)
if(center_x > 300)
center_x = 0;
if(center_x < 0)
center_x = 300;
if(center_y > 300)
center_y = 0;
if(center_y < 0)
center_y = 300;
This will take the ship to the other side of the screen when needed. However, we still need to show part of the ship while the other part is on the other side. For that reason, we can just render the ship twice. I hope the following code speaks for itself.
Rectangle2D box = transformed.getBounds2D();
//wrap in x direction
if(box.getX() + box.getWidth() > 300){
AffineTransform transform2 = new AffineTransform();
transform2.translate(ship.center_x - 300, ship.center_y);
transform2.rotate(Math.toRadians(ship.angle));
Shape transformed2 = transform2.createTransformedShape(ship);
g2.draw(transformed2);
}else if(box.getX() < 0){
AffineTransform transform2 = new AffineTransform();
transform2.translate(ship.center_x + 300, ship.center_y);
transform2.rotate(Math.toRadians(ship.angle));
Shape transformed2 = transform2.createTransformedShape(ship);
g2.draw(transformed2);
}
//wrap in y direction
if(box.getY() + box.getHeight() > 300){
AffineTransform transform2 = new AffineTransform();
transform2.translate(ship.center_x, ship.center_y - 300);
transform2.rotate(Math.toRadians(ship.angle));
Shape transformed2 = transform2.createTransformedShape(ship);
g2.draw(transformed2);
}else if(box.getY() < 0){
AffineTransform transform2 = new AffineTransform();
transform2.translate(ship.center_x, ship.center_y + 300);
transform2.rotate(Math.toRadians(ship.angle));
Shape transformed2 = transform2.createTransformedShape(ship);
g2.draw(transformed2);
}
Physics
You also requested more info on the actual physics, i'll just give you the formulas (let me know if you need more help). Here, theta is the angle of the ship in radians, where if theta is 0, the ship points up. The x-axis points to the right, and the y-axis points up.
a_x = Math.sin(theta)*a;
a_y = -Math.cos(theta)*a;
v_x_new = v_x_old + a_x*timeDiff;
v_y_new = v_y_old + a_y*timeDiff;
x_new = x_old + v_x_old*timeDiff + a_x*timeDiff*timeDiff/2;
y_new = y_old + v_y_old*timeDiff + a_y*timeDiff*timeDiff/2;
x_old = x_new;
y_old = y_new;
v_x_old = v_x_new;
v_y_old = v_y_new;
Final result
I've implemented how the ship class should look, and also tested if the rendering method works.
public class AsteroidsTest {
public static void main(String[] args){
int[] xPoints = {0, -20, 0, 20};
int[] yPoints = {-40, 20, 0, 20};
Ship ship = new Ship(xPoints, yPoints, 4, 0);
ship.center_x = 100;
ship.center_y = 100;
ship.angle = 45;
ship.speed_x = 30;
ship.speed_y = -30;
System.out.println("running...");
JFrame window = new JFrame();
window.setBounds(30, 30, 300, 300);
window.getContentPane().add(new MyCanvas(ship));
window.setVisible(true);
window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
long time1 = System.currentTimeMillis();
while(true){
long time2 = System.currentTimeMillis();
double timeDiff = (time2-time1)/1000f;
time1 = time2;
ship.move(timeDiff);
window.getContentPane().repaint();
}
}
}
class MyCanvas extends JComponent {
private Ship ship;
public MyCanvas(Ship ship){
this.ship = ship;
}
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
AffineTransform transform = new AffineTransform();
transform.translate(ship.center_x, ship.center_y);
transform.rotate(Math.toRadians(ship.angle));
Shape transformed = transform.createTransformedShape(ship);
g2.draw(transformed);
Rectangle2D box = transformed.getBounds2D();
//wrap in x direction
if(box.getX() + box.getWidth() > 300){
AffineTransform transform2 = new AffineTransform();
transform2.translate(ship.center_x - 300, ship.center_y);
transform2.rotate(Math.toRadians(ship.angle));
Shape transformed2 = transform2.createTransformedShape(ship);
g2.draw(transformed2);
}else if(box.getX() < 0){
AffineTransform transform2 = new AffineTransform();
transform2.translate(ship.center_x + 300, ship.center_y);
transform2.rotate(Math.toRadians(ship.angle));
Shape transformed2 = transform2.createTransformedShape(ship);
g2.draw(transformed2);
}
//wrap in y direction
if(box.getY() + box.getHeight() > 300){
AffineTransform transform2 = new AffineTransform();
transform2.translate(ship.center_x, ship.center_y - 300);
transform2.rotate(Math.toRadians(ship.angle));
Shape transformed2 = transform2.createTransformedShape(ship);
g2.draw(transformed2);
}else if(box.getY() < 0){
AffineTransform transform2 = new AffineTransform();
transform2.translate(ship.center_x, ship.center_y + 300);
transform2.rotate(Math.toRadians(ship.angle));
Shape transformed2 = transform2.createTransformedShape(ship);
g2.draw(transformed2);
}
}
}
Ship class:
import java.awt.Polygon;
public class Ship extends Polygon{
private static final long serialVersionUID = 1L;
public double center_x = 0;
public double center_y = 0;
public double speed_x = 0;
public double speed_y = 0;
//the angle of the ship
public double angle = 0;
//the acceleration of the ship in the direction defined by angle
public double acceleration = 0;
public Ship(int[] x, int[] y, int points, double angle){
super(x, y, points);
this.angle= angle;
}
public void right(){
angle += 5;
}
public void left(){
angle -= 5;
}
public void move(double timeDiff){
double a_x = Math.sin(Math.toRadians(angle))*acceleration;
double a_y = -Math.cos(Math.toRadians(angle))*acceleration;
center_x = center_x + speed_x*timeDiff + a_x*timeDiff*timeDiff/2;
center_y = center_y + speed_y*timeDiff + a_y*timeDiff*timeDiff/2;
speed_x = speed_x + a_x*timeDiff;
speed_y = speed_y + a_y*timeDiff;
if(center_x > 300)
center_x = 0;
if(center_x < 0)
center_x = 300;
if(center_y > 300)
center_y = 0;
if(center_y < 0)
center_y = 300;
}
public void reverse(){
acceleration = -1;
}
public void go(){
acceleration = 1;
}
public void stop(){
acceleration = 0;
}
public int getCenterX(){
return (int) Math.round(center_x);
}
public int getCenterY(){
return (int) Math.round(center_y);
}
public double getAng(){
return angle;
}
}
Related
Im trying to display the maximum number of smaller rectangles that will fit into a larger one. I can get the horizontal rectangles to show up, but the vertical rectangles will not show up. I need help on figuring out how to get the vertical rectangles to display in the JFrame.
Here is the class with the main:
import javax.swing.*;
public class MyPanel extends JComponent {
public static void main(String[] arguments) {
LargeRec large = new LargeRec();
JFrame.setDefaultLookAndFeelDecorated(true);
JFrame frame = new JFrame("Rectangle");
frame.setResizable(false);
frame.setVisible(true);
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);
frame.setSize(500,400);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(large);
}
}
This is the class for creating the rectangle's:
import javax.swing.*;
import java.awt.*;
public class LargeRec extends JComponent {
static int larX;
static int larY;
static int smaX;
static int smaY;
public static int getRandomIntRange(int min, int max) {
int x = (int) (Math.random() * ((max + 1 - min))) + min;
if (x > (max + 1))
x = max;
return x;
}
public void paint(Graphics g) {
larX = getRandomIntRange(100, 300);
larY = getRandomIntRange(100, 300);
while (larX == larY) {
larX = getRandomIntRange(100, 300);
larY = getRandomIntRange(100, 300);
}
while (larY > larX) {
larX = getRandomIntRange(100, 300);
larY = getRandomIntRange(100, 300);
}
smaX = getRandomIntRange(10, 50);
smaY = getRandomIntRange(10, 50);
while (smaX == smaY) {
smaX = getRandomIntRange(10, 50);
smaY = getRandomIntRange(10, 50);
}
while (smaY > smaX) {
smaX = getRandomIntRange(10, 50);
smaY = getRandomIntRange(10, 50);
}
g.drawRect(0, 0, larX, larY);
g.setColor(Color.black);
g.fillRect(0, 0, larX, larY);
int LX = larX;
int LY = larY;
int SX = smaX;
int SY = smaY;
for (int nx = 0; nx <= larX; nx = nx + smaX) {
while (LY >= SY) {
LY = LY - SY;
if(LX>=SX) {
g.setColor(Color.red);
g.drawRect(nx, LY, smaX, smaY);
}
}
LX = LX - SX;
LY = larY;
}
Graphics2D g2 = (Graphics2D) g;
for (int ny =LX; ny+smaY <=LX ; ny = ny + smaY) {
while (LY >= SX) {
g2.setColor(Color.blue);
g2.drawRect(ny, LY, smaY, smaX);
LY = LY - SX;
}
LY=larY;
}
}
}
It should output a black rectangle with smaller horizontal rectangles and vertical rectangles. It displays the horizontal (red) rectangles, but not the vertical(blue) rectangles.
Here is an example of a bad output:
I just started working with LibGDX and can't figure out how to hide an image when it collides with an object. In my game some dots come from the top of the screen and meet the dot at the bottom. When they meet the dots should hide that isn't happening.
This is the main Game Class
public class GameScreen implements Screen,InputProcessor {
final AmazingDot game;
//setting the height and width variables
private int height;
private int width;
private static int touchCounter;
//setting the two dots variables
private Texture playerDotImage;
private Texture gameDotImage;
private Texture gameDotImage1;
private Rectangle playerDotRectangle;
private Map<Rectangle,Texture> gameDotMap;
//storing the time of last dot in nano seconds
private long lastDotTime;
public GameScreen(final AmazingDot gam){
this.game = gam;
Gdx.input.setInputProcessor(this);
//getting the height and width of the user's screen
height = Gdx.graphics.getHeight();
width = Gdx.graphics.getWidth();
touchCounter =0;
//loading the images in the variables
playerDotImage = new Texture(Gdx.files.internal("images/dot2.png"));
gameDotImage = new Texture(Gdx.files.internal("images/dot1.png"));
gameDotImage1 = new Texture(Gdx.files.internal("images/dot2.png"));
//placing the player dot in the middle of the screen
playerDotRectangle = new Rectangle();
playerDotRectangle.x = width/ 2 - 64 / 2;
playerDotRectangle.y = 20;
playerDotRectangle.width = 64;
playerDotRectangle.height = 64;
gameDotMap = new ConcurrentHashMap<Rectangle, Texture>();
populateDots();
}
private void populateDots(){
Rectangle dots = new Rectangle();
dots.x = randomLocation();
dots.y = height;
dots.width = 64;
dots.height = 64;
Random rand = new Random();
int n = rand.nextInt(2) + 1;
if(n==1){
gameDotMap.put(dots,gameDotImage1);
}
else{
gameDotMap.put(dots,gameDotImage);
}
lastDotTime = TimeUtils.nanoTime();
}
private int randomLocation(){
int[] locations = new int[3];
locations[0]=0;
locations[1]=width/2-64/2;
locations[2]=width-64;
Random generator = new Random();
int randomIndex = generator.nextInt(locations.length);
return locations[randomIndex];
}
#Override
public void render(float delta) {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
game.batch.begin();
game.batch.draw(playerDotImage,playerDotRectangle.x,playerDotRectangle.y,playerDotRectangle.width,playerDotRectangle.height);
for(Map.Entry<Rectangle,Texture> dots : gameDotMap.entrySet()){
game.batch.draw(dots.getValue(),dots.getKey().x,dots.getKey().y);
}
game.batch.end();
// check if we need to create a new dot
if(TimeUtils.nanoTime() - lastDotTime > 1000000000) populateDots();
for(Rectangle dot : gameDotMap.keySet()){
int gameSpeed = 400;
int xSpeed = calXSpeed(gameSpeed);
dot.y = dot.y - gameSpeed * Gdx.graphics.getDeltaTime();
if(dot.x <width/2-64/2){
dot.x = dot.x + xSpeed*Gdx.graphics.getDeltaTime();
}
else if(dot.x>width/2-64/2){
dot.x = dot.x - xSpeed*Gdx.graphics.getDeltaTime();
}
if(dot.y + 64 < 0) gameDotMap.remove(dot);
if(dot.overlaps(playerDotRectangle)) {
//this is where I am trying to remove the map object on collision
gameDotMap.remove(dot);
}
}
}
private int calXSpeed(int gameSpeed){
int seconds = height/gameSpeed;
int distance = width/2-64/2;
int speed = distance/seconds;
return speed;
}
#Override
public boolean touchDown(int screenX, int screenY, int pointer, int button) {
touchCounter++;
if(touchCounter % 2==0){
playerDotImage = new Texture(Gdx.files.internal("images/dot2.png"));
}
else{
playerDotImage = new Texture(Gdx.files.internal("images/dot1.png"));
}
return true;
}
#Override
public void dispose() {
playerDotImage.dispose();
gameDotImage.dispose();
gameDotImage1.dispose();
}
}
EDIT
As you can see in the above image when the moving dot reaches the stationary dot, it should disappear. But here in my code the dot just moves past the stationary dot. I am using rectangles(LibGdx) to detect whether the dots overlap each other or not.
Define you Map as an ArrayMap like so:
private ArrayMap<Rectangle, Texture> gameDotMap;
Then you can rewrite you render method like this:
#Override
public void render(float delta) {
Gdx.gl.glClearColor(0, 0, 0, 1);
Gdx.gl.glClear(GL20.GL_COLOR_BUFFER_BIT);
game.batch.begin();
game.batch.draw(playerDotImage, playerDotRectangle.x, playerDotRectangle.y, playerDotRectangle.width, playerDotRectangle.height);
for (int i = 0; i < gameDotMap.size; i++) {
game.batch.draw(gameDotMap.getValueAt(i), gameDotMap.getKeyAt(i).x, gameDotMap.getKeyAt(i).y);
}
game.batch.end();
// check if we need to create a new dot
if (TimeUtils.nanoTime() - lastDotTime > 1000000000) {
populateDots();
}
for (Iterator<ObjectMap.Entry<Rectangle, Texture>> iter = gameDotMap.iterator(); iter.hasNext();) {
ObjectMap.Entry<Rectangle, Texture> entry = iter.next();
Rectangle dot = entry.key;
int gameSpeed = 400;
int xSpeed = calXSpeed(gameSpeed);
dot.y = dot.y - gameSpeed * Gdx.graphics.getDeltaTime();
if (dot.x < width / 2 - 64 / 2) {
dot.x = dot.x + xSpeed * Gdx.graphics.getDeltaTime();
} else if (dot.x > width / 2 - 64 / 2) {
dot.x = dot.x - xSpeed * Gdx.graphics.getDeltaTime();
}
if (dot.y + 64 < 0) {
iter.remove();
}
if (dot.overlaps(playerDotRectangle)) {
//this is where I am trying to remove the map object on collision
iter.remove();
}
}
}
LibGDX has specific types for collections, they are recommended over the JDK's collections.
I have a problem.
I am a beginner with java, and succeeded up to this point. Add bubbles with random sizes.
Now I need to make the bubbles escaping mouse when he gets near them.
Can anyone give me a hint how?
Thank you.
public class BounceBall extends JFrame {
private ShapePanel drawPanel;
private Vector<NewBall> Balls;
private JTextField message;
// set up interface
public BounceBall() {
super("MultiThreading");
drawPanel = new ShapePanel(400, 345);
message = new JTextField();
message.setEditable(false);
Balls = new Vector<NewBall>();
add(drawPanel, BorderLayout.NORTH);
add(message, BorderLayout.SOUTH);
setSize(400, 400);
setVisible(true);
}
public static void main(String args[]) {
BounceBall application = new BounceBall();
application.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
}
private class NewBall extends Thread {
private Ellipse2D.Double thisBall;
private boolean ballStarted;
private int size, speed; // characteristics
private int deltax, deltay; // of the ball
public NewBall() {
ballStarted = true;
size = 10 + (int) (Math.random() * 60);
speed = 10 + (int) (Math.random() * 100);
int startx = (int) (Math.random() * 300);
int starty = (int) (Math.random() * 300);
deltax = -10 + (int) (Math.random() * 21);
deltay = -10 + (int) (Math.random() * 21);
if ((deltax == 0) && (deltay == 0)) {
deltax = 1;
}
thisBall = new Ellipse2D.Double(startx, starty, size, size);
}
public void draw(Graphics2D g2d) {
if (thisBall != null) {
g2d.setColor(Color.BLUE);
g2d.fill(thisBall);
}
}
public void run() {
while (ballStarted) // Keeps ball moving
{
try {
Thread.sleep(speed);
} catch (InterruptedException e) {
System.out.println("Woke up prematurely");
}
// calculate new position and move ball
int oldx = (int) thisBall.getX();
int oldy = (int) thisBall.getY();
int newx = oldx + deltax;
if (newx + size > drawPanel.getWidth() || newx < 0) {
deltax = -deltax;
}
int newy = oldy + deltay;
if (newy + size > drawPanel.getHeight() || newy < 0) {
deltay = -deltay;
}
thisBall.setFrame(newx, newy, size, size);
drawPanel.repaint();
}
}
}
private class ShapePanel extends JPanel {
private int prefwid, prefht;
public ShapePanel(int pwid, int pht) {
prefwid = pwid;
prefht = pht;
// add ball when mouse is clicked
addMouseListener(
new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
NewBall nextBall = new NewBall();
Balls.addElement(nextBall);
nextBall.start();
message.setText("Number of Balls: " + Balls.size());
}
});
}
public Dimension getPreferredSize() {
return new Dimension(prefwid, prefht);
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
for (int i = 0; i < Balls.size(); i++) {
(Balls.elementAt(i)).draw(g2d);
}
}
}
}
You should not have a Thread for each individual ball, this will not scale well, the more balls you add, the more threads you add. At some point, the amount of work it takes to manage the threads will exceed the benefit for using multiple threads...
Also, I doubt if your need 1000fps...something like 25fps should be more than sufficient for your simple purposes. This will give the system some breathing room and allow other threads within the system time to execute.
Lets start with a simple concept of a Ball. The Ball knows where it is and which direction it is moving it, it also knows how to paint itself, for example...
public class Ball {
private int x;
private int y;
private int deltaX;
private int deltaY;
private int dimeter;
private Ellipse2D ball;
private Color color;
public Ball(Color color, Dimension bounds) {
this.color = color;
Random rnd = new Random();
dimeter = 5 + rnd.nextInt(15);
x = rnd.nextInt(bounds.width - dimeter);
y = rnd.nextInt(bounds.height - dimeter);
if (x < 0) {
x = 0;
}
if (y < 0) {
y = 0;
}
int maxSpeed = 10;
do {
deltaX = rnd.nextInt(maxSpeed) - (maxSpeed / 2);
} while (deltaX == 0);
do {
deltaY = rnd.nextInt(maxSpeed) - (maxSpeed / 2);
} while (deltaY == 0);
ball = new Ellipse2D.Float(0, 0, dimeter, dimeter);
}
public void update(Dimension bounds) {
x += deltaX;
y += deltaY;
if (x < 0) {
x = 0;
deltaX *= -1;
} else if (x + dimeter > bounds.width) {
x = bounds.width - dimeter;
deltaX *= -1;
}
if (y < 0) {
y = 0;
deltaY *= -1;
} else if (y + dimeter > bounds.height) {
y = bounds.height - dimeter;
deltaY *= -1;
}
}
public void paint(Graphics2D g2d) {
g2d.translate(x, y);
g2d.setColor(color);
g2d.fill(ball);
g2d.translate(-x, -y);
}
}
Next, we need somewhere for the balls to move within, some kind of BallPit for example...
public class BallPit extends JPanel {
private List<Ball> balls;
public BallPit() {
balls = new ArrayList<>(25);
balls.add(new Ball(Color.RED, getPreferredSize()));
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (Ball ball : balls) {
ball.update(getSize());
}
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
for (Ball ball : balls) {
ball.paint(g2d);
}
g2d.dispose();
}
}
This maintains a list of balls, tells them when the need to update and when the need to paint. This example uses a simple javax.swing.Timer, which acts as the central timer which updates the balls and schedules the repaints.
The reason for this is takes care of synchronisation between the updates and the paints, meaning that the balls won't be updating while they are been painted. This is achieved because javax.swing.Timer triggers it's callbacks within the context of the EDT.
See Concurrency in Swing and How to use Swing Timers for more details.
Okay, so that fixes the threading issues, but what about the mouse avoidance...
That's a little more complicated...
What we need to is add a MouseMoitionListener to the BillPit and record the last position of the mouse.
public class BallPit extends JPanel {
//...
private Point mousePoint;
//...
public BallPit() {
//...
MouseAdapter handler = new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
mousePoint = e.getPoint();
}
#Override
public void mouseExited(MouseEvent e) {
mousePoint = null;
}
};
addMouseListener(handler);
addMouseMotionListener(handler);
//...
The reason for including mouseExit is to ensure that balls don't try and move away from a phantom mouse cursor...
Next, we need to update Ball to have an "area of effect", this is the area around the ball that will trigger a change in movement if the mouse cursor moves within it's range...
public class Ball {
//...
private final Ellipse2D.Float areaOfEffect;
public Ball(Color color, Dimension bounds) {
//...
areaOfEffect = new Ellipse2D.Float(-10, -10, dimeter + 20, dimeter + 20);
}
Now, I also add some additional painting for debug reasons...
public void paint(Graphics2D g2d) {
g2d.translate(x, y);
g2d.setColor(new Color(0, 0, 192, 32));
g2d.fill(areaOfEffect);
g2d.setColor(color);
g2d.fill(ball);
g2d.translate(-x, -y);
}
Next, we need to modify the Ball's update method to accept the mousePoint value...
public void update(Dimension bounds, Point mousePoint) {
PathIterator pathIterator = areaOfEffect.getPathIterator(AffineTransform.getTranslateInstance(x, y));
GeneralPath path = new GeneralPath();
path.append(pathIterator, true);
if (mousePoint != null && path.contains(mousePoint)) {
// Determine which axis is closes to the cursor...
int xDistance = Math.abs(x + (dimeter / 2) - mousePoint.x);
int yDistance = Math.abs(y + (dimeter / 2) - mousePoint.y);
if (xDistance < yDistance) {
// If x is closer, the change the delatX
if (x + (dimeter / 2) < mousePoint.x) {
if (deltaX > 0) {
deltaX *= -1;
}
} else {
if (deltaX > 0) {
deltaX *= -1;
}
}
} else {
// If y is closer, the change the deltaY
if (y + (dimeter / 2) < mousePoint.y) {
if (deltaY > 0) {
deltaY *= -1;
}
} else {
if (deltaY > 0) {
deltaY *= -1;
}
}
}
}
//...Rest of previous method code...
}
Basically, what this is trying to do is determine which axis is closer to the mouse point and in which direction the ball should try and move...it's a little "basic", but gives the basic premise...
Lastly, we need to update the "update" loop in the javax.swing.Timer to supply the additional parameter
for (Ball ball : balls) {
ball.update(getSize(), mousePoint);
}
I'm going to answer this, but I'm very close to issuing a close vote because it doesn't show what you've done so far to attempt this. I would not be surprised if others are closer to the edge than I am on this. At the same time, you've clearly shown your progress before you reached this point, so I'll give you the benefit of the doubt. In the future, I would strongly advise making an attempt and then posting a question that pertains to the specific problem you're having while making that attempt.
You need two things:
The current location of the mouse
A range check and reversal of direction if too close.
The location of the mouse can be achieved by adding two variables (x and y) and, every time the mouse is moved (so add a mouse event listener to your JPanel or something) update those variables with the new location.
Then, you can do a range check (think Pythagorean theorem) on each bubble to make sure they're far enough away. If the bubble is too close, you'll want to check where that bubble would end up if it carried on its current course, as well as where it would end up if it changed X direction, Y direction, or both. Pick the one that ends up being furthest away and set the deltax and deltay to those, and let the calculation carry on as normal.
It sounds like a lot, but those are the two basic components you need to achieve this.
I am making a circle to circle collision detection program. I can get the balls to move around but when the collision is detected, the balls are quite far overlapped. Any suggestions? Thanks in advance!
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.ArrayList;
import java.lang.Math;
public class ShapePanel extends JPanel{
private JButton button, startButton, stopButton;
private JTextField textField;
private JLabel label;
private Timer timer;
private final int DELAY = 10;
ArrayList<Shape> obj = new ArrayList<Shape>();
public static void main(String[] args){
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new ShapePanel());
frame.pack();
frame.setVisible(true);
}
public ShapePanel(){
JPanel controlPanel = new JPanel();
DrawingPanel dpanel = new DrawingPanel();
controlPanel.setPreferredSize(new Dimension(100,400));
button = new JButton("Add Shape");
startButton = new JButton("Start");
stopButton = new JButton("Stop");
textField = new JTextField(2);
label = new JLabel("Count:");
controlPanel.add(button);
controlPanel.add(label);
controlPanel.add(textField);
controlPanel.add(startButton);
controlPanel.add(stopButton);
add(controlPanel);
add(dpanel);
ButtonListener bListen = new ButtonListener();
button.addActionListener(bListen);
startButton.addActionListener(bListen);
stopButton.addActionListener(bListen);
timer = new Timer(DELAY, bListen);
}
private class ButtonListener implements ActionListener{
public void actionPerformed(ActionEvent e){
if (e.getSource() == button){
obj.add(new Shape());
if (obj.get(obj.size()-1).y > 200){
obj.get(obj.size()-1).moveY = -obj.get(obj.size()-1).moveY;
}
}else if (e.getSource() == timer){
for (int i = 0; i < obj.size(); i++){
obj.get(i).move();
}
for (int i = 0; i < obj.size(); i++){
for (int j = i + 1; j < obj.size(); j++){
if (Math.sqrt(Math.pow((double)obj.get(i).centerCoordX - (double)obj.get(j).centerCoordX,2)) +
Math.pow((double)obj.get(i).centerCoordY - (double)obj.get(j).centerCoordY,2) <= obj.get(i).radius + obj.get(j).radius){
timer.stop();
}
}
}
}else if (e.getSource() == startButton){
timer.start();
}else if (e.getSource() == stopButton){
timer.stop();
}
repaint();
}
}
private class DrawingPanel extends JPanel{
DrawingPanel(){
setPreferredSize(new Dimension(400,400));
setBackground(Color.pink);
}
public void paintComponent(Graphics g){
super.paintComponent(g);
for(int i = 0; i < obj.size(); i++){
obj.get(i).display(g);
}
}
}
}
import java.awt.*;
import java.util.*;
public class Shape{
public int x, y, width, height, moveX = 1, moveY = 1, centerCoordX, centerCoordY, radius;
private Color colour;
public boolean reverse = false, sameDirection = true;
Random generator = new Random();
public int randomRange(int lo, int hi){
return generator.nextInt(hi-lo)+lo;
}
Shape(){
width = randomRange(30, 50);
if (width % 2 != 0){
width = randomRange(30, 50);
}
height = width;
radius = width/2;
x = randomRange(0, 400-width);
y = randomRange(0, 400-height);
colour = new Color(generator.nextInt(256),generator.nextInt(256),generator.nextInt(256));
}
public void display(Graphics g){
g.setColor(colour);
g.fillOval(x, y, width, height);
}
void move(){
x += moveX;
y += moveY;
centerCoordX = x + width/2;
centerCoordY = y + height/2;
if(x >= 400-width){
moveX = -moveX;
}if(x <= 0){
moveX = -moveX;
}if(y >= 400-height){
moveY = -moveY;
}if (y <= 0){
moveY = -moveY;
}
}
}
So much uncommented code!
The balls are just colliding if their centres are within the sum of the radii. Let r1 and r2 be the ball radii, and x1, y1 the position of the centre of ball1; similarly x2, y2 for ball2.
Measure the square of the distance between the centres as (x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1). (Pythagoras).
They have collided if this is less than or equal to (r1 + r2) * (r1 + r2).
The key thing here is that there is no need to compute the square roots which is an expensive computational task.
I'd say it probably due to your circles moving too fast or your time step is too high. Reduce your time step. A better approach is use a physics library like Box2D. Have a look at libgdx, that a java library that has it included.
its discussed a bit in this link
You should check the colision after each move.
for (int i = 0; i < obj.size(); i++)
{
obj.get(i).move();
for (int j = 0; j < obj.size(); j++)
{
if (Math.sqrt(Math.pow((double)obj.get(i).centerCoordX - (double)obj.get(j).centerCoordX,2)) +
Math.pow((double)obj.get(i).centerCoordY - (double)obj.get(j).centerCoordY,2) <= obj.get(i).radius + obj.get(j).radius && i!=j)
{
timer.stop();
}
}
}
You need something like this.
public boolean overlaps (Circle c1, Circle c2) {
float dx = c1.x - c2.x;
float dy = c1.y - c2.y;
float distance = dx * dx + dy * dy;
float radiusSum = c1.radius + c2.radius;
return distance < radiusSum * radiusSum;
}
I have an assignment that requires us to paint a tree of Pythagoras using recursion. The tree is started with the square ABCD and the points A and B are defined by mouse clicks. Everything seems to work until I get to the recursion where I can get either the left or right part of the tree to paint, but not both. I placed a comment where I believe I am running into problems.
import java.awt.*;
import java.awt.event.*;
import java.util.Random;
import javax.swing.*;
public class PythagorasTree extends JFrame
{
public static void main(String[] args)
{
new PythagorasTree();
}
PythagorasTree()
{
super("Pythagoras Tree");
setSize(800,800);
add("Center", new DrawingPanel());
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
class DrawingPanel extends JPanel
{
Random random = new Random();
int centerX;
int centerY;
int clickCount = 0;
float pixelSize;
float rWidth = 10.0F;
float rHeight = 7.5F;
float red, green, blue;
Point a = new Point();
Point b = new Point();
Point c = new Point();
Point d = new Point();
Point e = new Point();
Point u = new Point();
DrawingPanel()
{
addMouseListener(new MouseAdapter()
{
public void mousePressed(MouseEvent click)
{
clickCount++;
if (clickCount == 1)
{
a.x = fx(click.getX());
a.y = fy(click.getY());
repaint();
}
if (clickCount == 2)
{
b.x = fx(click.getX());
b.y = fy(click.getY());
repaint();
}
}
});
}
void initgr()
{
Dimension d = getSize();
int maxX = d.width - 1;
int maxY = d.height - 1;
pixelSize = Math.max(rWidth/maxX, rHeight/maxY);
centerX = maxX/2;
centerY = maxY/2;
}
int iX(float x){return Math.round(centerX + x/pixelSize);}
int iY(float y){return Math.round(centerY - y/pixelSize);}
float fx(int x){return (x - centerX) * pixelSize;}
float fy(int y){return (centerY - y) * pixelSize;}
public void paintComponent(Graphics g)
{
initgr();
super.paintComponent(g);
setBackground(Color.white);
g.setColor(Color.red);
if (clickCount == 1)
g.drawLine(iX(a.x), iY(a.y), iX(a.x), iY(a.y));
if (clickCount > 1)
drawTree(g,a,b);
}
public void drawTree(Graphics g, Point first, Point second)
{
float xSquared = (float) Math.pow((second.x-first.x),2);
float ySquared = (float) Math.pow((second.y-first.y),2);
float length = (float) Math.sqrt(xSquared + ySquared);
if ( length > .001)
{
u.x = second.x - first.x;
u.y = second.y - first.y;
a.x = first.x;
a.y = first.y;
b.x = second.x;
b.y = second.y;
d.x = first.x + (u.y * -1);
d.y = first.y + u.x;
c.x = d.x + u.x;
c.y = d.y + u.y;
e.x = d.x + .5F * (u.x + (u.y*-1));
e.y = d.y + .5F * (u.y + u.x);
Polygon square = new Polygon();
Polygon triangle = new Polygon();
square.addPoint(iX(a.x), iY(a.y));
square.addPoint(iX(b.x), iY(b.y));
square.addPoint(iX(c.x), iY(c.y));
square.addPoint(iX(d.x), iY(d.y));
red = random.nextFloat();
green = random.nextFloat();
blue = random.nextFloat();
g.setColor(new Color(red, green, blue));
g.fillPolygon(square);
triangle.addPoint(iX(c.x), iY(c.y));
triangle.addPoint(iX(d.x), iY(d.y));
triangle.addPoint(iX(e.x), iY(e.y));
red = random.nextFloat();
green = random.nextFloat();
blue = random.nextFloat();
g.setColor(new Color(red, green, blue));
g.fillPolygon(triangle);
/* Problem code is here, tree will draw Left or Right depending on which recursive call
* is first in the code, but will never reach the 2nd call.
*/
drawTree(g,d,e); //Draw tree left
drawTree(g,e,c); //Draw tree right
}
}
}
class Point
{
public float x;
public float y;
public Point()
{
}
}
It's time this question had an answer. The problem is with these member variable declarations:
Point c = new Point();
Point e = new Point();
And this code in drawTree():
drawTree(g,d,e); //Draw tree left
drawTree(g,e,c); //Draw tree right
Since c and e are a member variables, not local, they get modified by the first recursive call to drawTree(g, d, e) so by the time we make the second call to drawTree(g, e, c), it's no longer the same c and e we thought we had. The following rework of the code makes these local (clearly not as efficient GC-wise, but also not as buggy) as well as several other small modifications:
import java.awt.*;
import java.awt.event.*;
import java.util.Random;
import javax.swing.*;
class Point
{
public float x;
public float y;
public Point(float x, float y) {
this.x = x;
this.y = y;
}
public Point() {
}
}
class DrawingPanel extends JPanel
{
Random random = new Random();
int clickCount = 0;
float pixelSize;
float rWidth = 10.0F;
float rHeight = 7.5F;
float red, green, blue;
Point a = new Point();
Point b = new Point();
Point center = new Point();
DrawingPanel()
{
addMouseListener(new MouseAdapter()
{
public void mousePressed(MouseEvent click)
{
clickCount++;
if (clickCount == 1)
{
a.x = fx(click.getX());
a.y = fy(click.getY());
repaint();
}
else if (clickCount == 2)
{
b.x = fx(click.getX());
b.y = fy(click.getY());
repaint();
}
}
});
}
void initgr()
{
Dimension d = getSize();
int maxX = d.width - 1;
int maxY = d.height - 1;
pixelSize = Math.max(rWidth / maxX, rHeight / maxY);
center.x = maxX / 2;
center.y = maxY / 2;
}
int iX(float x){ return Math.round(center.x + x / pixelSize); }
int iY(float y){ return Math.round(center.y - y / pixelSize); }
float fx(int x){ return (x - center.x) * pixelSize; }
float fy(int y){ return (center.y - y) * pixelSize; }
public void paintComponent(Graphics g)
{
super.paintComponent(g);
initgr();
setBackground(Color.white);
if (clickCount == 1)
{
g.setColor(Color.red);
g.drawLine(iX(a.x), iY(a.y), iX(a.x), iY(a.y));
}
else if (clickCount > 1) {
drawTree(g, a, b);
}
}
public void drawTree(Graphics g, Point first, Point second)
{
double xSquared = Math.pow(second.x - first.x, 2);
double ySquared = Math.pow(second.y - first.y, 2);
if (Math.sqrt(xSquared + ySquared) < 0.01) {
return;
}
Point u = new Point(second.x - first.x, second.y - first.y);
Point d = new Point(first.x - u.y, first.y + u.x);
Point c = new Point(d.x + u.x, d.y + u.y);
Point e = new Point(d.x + 0.5F * (u.x - u.y), d.y + 0.5F * (u.y + u.x));
Polygon square = new Polygon();
square.addPoint(iX(first.x), iY(first.y));
square.addPoint(iX(second.x), iY(second.y));
square.addPoint(iX(c.x), iY(c.y));
square.addPoint(iX(d.x), iY(d.y));
red = random.nextFloat();
green = random.nextFloat();
blue = random.nextFloat();
g.setColor(new Color(red, green, blue));
g.fillPolygon(square);
Polygon triangle = new Polygon();
triangle.addPoint(iX(c.x), iY(c.y));
triangle.addPoint(iX(d.x), iY(d.y));
triangle.addPoint(iX(e.x), iY(e.y));
red = random.nextFloat();
green = random.nextFloat();
blue = random.nextFloat();
g.setColor(new Color(red, green, blue));
g.fillPolygon(triangle);
drawTree(g, d, e); // Draw tree left
drawTree(g, e, c); // Draw tree right
}
}
public class PythagorasTree extends JFrame
{
PythagorasTree()
{
super("Pythagoras Tree");
setSize(800, 800);
add("Center", new DrawingPanel());
setVisible(true);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public static void main(String[] args)
{
new PythagorasTree();
}
}
OUTPUT