Related
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 9 months ago.
Improve this question
I have a tank game where you rotate via A/D keys and move forward/backward with W/S. The turret follows the mouse cursor on the screen. I am attempting to get shooting to work when I click. I get the initial degree of the turret rotation on click, as well as the point at the end of the turret. I set isShooting() to check if I am currently shooting at the given moment, and it is set to true on click. I use shotFired() also set to true on click, to tell whether there is a shot on the screen.
I want to have the bullet moving along a straight line with a give slope every time I click, from the end of the barrel to off the screen.
Currently the bullet moves in the wrong directions and is influenced by movement of the turret. I have been working to find the issue and cannot find it.
public void update() {
playerTank.setCenterTurret(new Point2D.Double(playerTank.xPos() + 67, playerTank.yPos() + 125));
playerTank.setEndTurret(new Point2D.Double(playerTank.xPos() + ((Math.sin(Math.toRadians(playerTank.getTurretDegree())))) + 63, playerTank.yPos() + ((Math.cos(Math.toRadians(playerTank.getTurretDegree())))) - 25));
playerTank.setCenterBase(new Point2D.Double(playerTank.xPos() + (playerTank_PNG_WIDTH / 2), playerTank.yPos() + (playerTank_PNG_HEIGHT / 2)));
mouseLoc = MouseInfo.getPointerInfo().getLocation();
SwingUtilities.convertPointFromScreen(mouseLoc, this);
mouseLocX = (int) mouseLoc.getX();
mouseLocY = (int) mouseLoc.getY();
mouseDistX = mouseLocX - playerTank.getCenterTurret().getX();
mouseDistY = mouseLocY - playerTank.getCenterTurret().getY();
mouseDegree = angleInRelation(mouseLoc, playerTank.getCenterTurret());
if(moveUp) {
playerTank.setLocation(playerTank.xPos() + (MOVEMENT_SPEED * Math.sin(Math.toRadians(playerTank.getBaseDegree()))), playerTank.yPos() - MOVEMENT_SPEED * Math.cos(Math.toRadians(playerTank.getBaseDegree())));
}
if(moveDown) {
playerTank.setLocation(playerTank.xPos() - (MOVEMENT_SPEED * Math.sin(Math.toRadians(playerTank.getBaseDegree()))), playerTank.yPos() + MOVEMENT_SPEED * Math.cos(Math.toRadians(playerTank.getBaseDegree())));
}
if(rotateLeft && playerTank.xPos() >= 0) {
playerTank.setBaseDegree(playerTank.getBaseDegree() - 5);
}
if(rotateRight && playerTank.xPos() + playerTank.getWidth() <= FRAME_WIDTH) {
playerTank.setBaseDegree(playerTank.getBaseDegree() + 5);
}
mouseDegree -= playerTank.getBaseDegree();
this.setBackground(Color.white);
repaint();
}
#Override
public void paint(Graphics g) {
this.setBackground(Color.white);
Graphics2D g2D = (Graphics2D) g;
g2D.setBackground(Color.white);
g2D.setColor(Color.white);
g2D.fillRect(0, 0, FRAME_WIDTH, FRAME_HEIGHT);
paintBase(g2D);
playerTank.setTurretDegree(mouseDegree);
g2D.rotate(Math.toRadians(playerTank.getTurretDegree()), playerTank.xPos() + 67, playerTank.yPos() + 125);
paintTurret(g2D);
paintBullet(g2D);
}
public void paintBullet(Graphics2D g2D) {
g2D.setColor(Color.black);
if(playerTank.isShooting()) {
playerTank.setBulletPos(playerTank.getEndTurret());
g2D.fillRect((int) playerTank.getEndTurret().getX(), (int) playerTank.getEndTurret().getY(), 8, 18);
playerTank.setShooting(false);
}
if (playerTank.shotFired()) {
double newX = (BULLET_SPEED * (Math.sin(Math.toRadians(playerTank.getInitialTurretDegree()))));
double newY = (BULLET_SPEED * (Math.cos(Math.toRadians(playerTank.getInitialTurretDegree()))));
playerTank.setBulletPos(playerTank.getBulletX() + newX, playerTank.getBulletY() - newY);
g2D.fillRect((int) playerTank.getBulletX(), (int) playerTank.getBulletY(), 8, 18);
if((playerTank.getBulletX() > FRAME_WIDTH || playerTank.getBulletY() > FRAME_HEIGHT) || (playerTank.getBulletX() < 0 || playerTank.getBulletY() < 0)) {
playerTank.setShotFired(false);
}
}
Thank you so much.
So, your problem basically boils down to a series of trigonometry problems ... yea (can you feel the sarcasm ๐).
But what do I mean? So, you want to find the end point of the barrel, based on it's current angle of rotation? Well, you will need to know the length of barrel, this will give you a radius around which the barrel can travel, from that you can simply perform a "point on a circle" calculation.
Want to know the path of the projectile, calculate the "point on a circle" based on a radius greater than the visible area and then draw a line between the barrel and this point, that's your path. Told you, trigonometry.
So, I started by creating a simple entity which would end up looking something like (with color guides)...
The turret and the body are seperate images, but when drawn on top of each other, they will appear as above, this is really important, as this saves so much effort.
The turret then has a specified radius and can rotate easily around the body
If you look really hard, the turret is actually not centered around the "natural" center of the image, but by laying it out this way, we can easily rotate around the mid point of the image - so much easier.
Okay, that all sounds fun, let's start with a basic concept of our "tank" entity...
public class Tank {
private BufferedImage body;
private BufferedImage turret;
private int x;
private int y;
private double bodyAngle = 0;
private double turretAngle = 0;
private int width;
private int height;
private int midX;
private int midY;
public Tank() throws IOException {
body = ImageIO.read(getClass().getResource("/images/TankBody.png"));
turret = ImageIO.read(getClass().getResource("/images/TankTurret.png"));
width = body.getWidth();
height = body.getHeight();
midX = width / 2;
midY = height / 2;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public double getBodyAngle() {
return bodyAngle;
}
public double getTurretAngle() {
return turretAngle;
}
public void setBodyAngle(double bodyAngle) {
this.bodyAngle = bodyAngle;
}
public void setTurretAngle(double turretAngle) {
this.turretAngle = turretAngle;
}
// This represents the "unrotated" width
public int getWidth() {
return width;
}
// This represents the "unrotated" height
public int getHeight() {
return height;
}
protected int getMidX() {
return midX;
}
protected int getMidY() {
return midY;
}
protected BufferedImage getBody() {
return body;
}
protected BufferedImage getTurret() {
return turret;
}
public void paint(Graphics2D master, ImageObserver observer) {
Graphics2D g2d = (Graphics2D) master.create();
g2d.translate(getX() - getMidX(), getY() - getMidY());
g2d.setColor(Color.RED);
g2d.drawOval(0, 0, getWidth(), getHeight());
Graphics2D bodyG = (Graphics2D) g2d.create();
bodyG.rotate(Math.toRadians(getBodyAngle()), getMidX(), getMidY());
// >>> Debug
bodyG.setColor(Color.ORANGE.darker());
bodyG.drawRect(0, 0, 64, 64);
// <<< Debug
bodyG.drawImage(getBody(), 0, 0, observer);
bodyG.dispose();
Graphics2D turrtG = (Graphics2D) g2d.create();
turrtG.rotate(Math.toRadians(getTurretAngle()), getMidX(), getMidY());
// >>> Debug
turrtG.setColor(Color.GREEN.darker());
// I mesured the turrent size in a image editor
// The acutal image size is the same as the body
// in order to make the workflow simpler
turrtG.drawRect((getWidth() - 20) / 2, 0, 20, 44);
// <<< Debug
turrtG.drawImage(getTurret(), 0, 0, observer);
turrtG.dispose();
g2d.dispose();
}
}
The important things to note here are...
The x/y position of the tank represents it's "center" point
The body and turret can rotate independently of each other
Now, we can make the turret look at the mouse by using a MouseMotionListener and, you guessed it, some more trigonometry
public class GamePane extends JPanel {
private Tank tank;
private Point mousePoint;
public GamePane() throws IOException {
tank = new Tank();
addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
mousePoint = e.getPoint();
}
});
tank.setX(200);
tank.setY(200);
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (mousePoint != null) {
double deltaX = tank.getX() - mousePoint.x;
double deltaY = tank.getY() - mousePoint.y;
// Because the image is pointing up, we need to offset
// the rotation by 90 for the API
double rotation = Math.toDegrees(Math.atan2(deltaY, deltaX) - Math.toRadians(90));
tank.setTurretAngle(rotation);
}
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
tank.paint(g2d, this);
// I don't trust the tanks paint process ;)
g2d.dispose();
g2d = (Graphics2D) g.create();
// This is all debug
if (mousePoint != null) {
// The radius around the tank based on the mouse's current location
double radius = Point2D.distance(tank.getX(), tank.getY(), mousePoint.x, mousePoint.y);
g2d.setColor(Color.MAGENTA);
g2d.draw(new Ellipse2D.Double(
tank.getX() - radius,
tank.getY() - radius,
radius * 2,
radius * 2));
}
g2d.dispose();
}
}
The "core" functionality is in the Timer and looks like...
double deltaX = tank.getX() - mousePoint.x;
double deltaY = tank.getY() - mousePoint.y;
// Because the image is pointing up, we need to offset
// the rotation by 90 for the API
double rotation = Math.toDegrees(Math.atan2(deltaY, deltaX) - Math.toRadians(90));
It's important to note that the Graphics API and Math API have a different concept of 0 (yea).
Okay, but how do we find the projectile path?! Well, more trigonometry!
But first, we need some help...
public class Util {
public static Point2D getPointOnCircle(double degress, double offset, double radius) {
double rads = Math.toRadians(degress + offset); // 0 becomes the top
// Calculate the outter point of the line
double xPosy = Math.cos(rads) * radius;
double yPosy = Math.sin(rads) * radius;
return new Point2D.Double(xPosy, yPosy);
}
public static Point2D getPointOnCircle(double degress, double offset, double radius, double centerX, double centerY) {
Point2D poc = getPointOnCircle(degress, offset, radius);
return new Point2D.Double(poc.getX() + centerX, poc.getY() + centerY);
}
}
So, all this does is provide some basic "point on circle" calculations. From this, we can calculate the "point" in the "world" we're looking for...
So, we can add...
public Point2D getBusinessEndOfBarrel() {
// I've deliberatly set up the images to be the same size, so this
// can be made easier. If your turren is a different size/position
// then you will need calculate this yourself
// Also, we're calculating this in "world" space
int centerX = getX();
int centerY = getY();
return Util.getPointOnCircle(getTurretAngle(), -90, Math.max(getWidth(), getHeight()) / 2, centerX, centerY);
}
to our Tank entity, this will return the "world" coordinates that represents the end of the barrel.
Let's add some "debug" graphics to our paintComponent...
// This is all debug
if (mousePoint != null) {
// The radius around the tank based on the mouse's current location
double radius = Point2D.distance(tank.getX(), tank.getY(), mousePoint.x, mousePoint.y);
g2d.setColor(Color.MAGENTA);
g2d.draw(new Ellipse2D.Double(
tank.getX() - radius,
tank.getY() - radius,
radius * 2,
radius * 2));
// From point, base the turrets current angle
Point2D fromPoint = tank.getBusinessEndOfBarrel();
// Mid point of the tank in the world
int worldMidX = tank.getX();
int worldMidY = tank.getY();
// The point on the circle where the mouse is, based on the turrents current angle
// which the diection the turret is pointing
Point2D toPoint = Util.getPointOnCircle(tank.getTurretAngle(), -90, radius, worldMidX, worldMidY);
// The "out of view" radius, this represents the "end point" for our projectile, because, it's easier
// to calculate
double outOfViewRadius = Math.max(getWidth(), getHeight()) * 2d;
Point2D outOfViewToPoint = Util.getPointOnCircle(tank.getTurretAngle(), -90, outOfViewRadius, getWidth() / 2, getHeight() / 2);
g2d.setColor(Color.CYAN);
// The projectiles path
g2d.draw(new Line2D.Double(fromPoint, outOfViewToPoint));
// A line from the barrel to the mouse point
g2d.setColor(Color.BLUE);
g2d.draw(new Line2D.Double(fromPoint, toPoint));
}
This will add...
A guide which represents the target circle created by the mouse
A projectile line from the barrel to the mouse
A projectile line from the barrel to outside the visible area
You might be surprised that this actually now provides the answer to the question. You have the start point and end point of the projectile, now you just need some way to animate it...
When it comes to animation, I tend to prefer time based animations, but since the projectile might travel a variable distance, what we really need is a linear progression, so if it was to travel a long distance or a short distance, it would travel at the same speed.
Now, I banged by head against Google for a bit and was able to come up with...
public class Projectile {
private Point2D from;
private Point2D to;
private Point2D current;
private long lastUpdate = 0;
private double deltaX;
private double deltaY;
public Projectile(Point2D from, Point2D to) {
this.from = from;
this.to = to;
current = new Point2D.Double(from.getX(), from.getY());
double deltaX = from.getX() - to.getX();
double deltaY = from.getY() - to.getY();
double angle = Math.atan2(deltaY, deltaX) + Math.toRadians(180);
this.deltaY = Math.sin(angle) * 100;
this.deltaX = Math.cos(angle) * 100;
lastUpdate = System.currentTimeMillis();
}
public Point2D getFrom() {
return from;
}
public Point2D getTo() {
return to;
}
public Point2D getLocation() {
return current;
}
public void tick() {
long elapsedTime = System.currentTimeMillis() - lastUpdate;
lastUpdate = System.currentTimeMillis();
double x = current.getX();
double y = current.getY();
x += deltaX * (elapsedTime / 1000d);
y += deltaY * (elapsedTime / 1000d);
current.setLocation(x, y);
}
}
This will basically calculate a x/y delta which needs to be applied to move the projectile from the current point to it's target point using a constant speed.
Now we can add a MouseListener...
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
int worldMidX = tank.getX();
int worldMidY = tank.getY();
Point2D fromPoint = tank.getBusinessEndOfBarrel();
double outOfViewRadius = Math.max(getWidth(), getHeight()) * 2d;
Point2D toPoint = Util.getPointOnCircle(tank.getTurretAngle(), -90, outOfViewRadius, worldMidX, worldMidY);
Projectile projectile = new Projectile(fromPoint, toPoint);
projectiles.add(projectile);
}
});
Update the projectiles in our main loop...
List<Projectile> outOfScopeProjectiles = new ArrayList<>(8);
Rectangle visibleBounds = new Rectangle(0, 0, getWidth(), getHeight());
for (Projectile projectile : projectiles) {
projectile.tick();
Point2D current = projectile.getLocation();
if (!visibleBounds.contains(current)) {
outOfScopeProjectiles.add(projectile);
}
}
projectiles.removeAll(outOfScopeProjectiles);
And update our paintComponent...
g2d.setColor(Color.RED);
for (Projectile projectile : projectiles) {
Point2D current = projectile.getLocation();
g2d.fill(new Ellipse2D.Double(current.getX() - 2, current.getY() - 2, 4, 4));
// >>> DEBUG
g2d.draw(new Line2D.Double(projectile.getFrom(), projectile.getTo()));
// << DEBUG
}
Runnable example...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.awt.image.ImageObserver;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
JFrame frame = new JFrame();
frame.add(new GamePane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
}
public class GamePane extends JPanel {
private Tank tank;
private Point mousePoint;
private List<Projectile> projectiles = new ArrayList<>(8);
public GamePane() throws IOException {
tank = new Tank();
addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseMoved(MouseEvent e) {
mousePoint = e.getPoint();
}
});
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
int worldMidX = tank.getX();
int worldMidY = tank.getY();
Point2D fromPoint = tank.getBusinessEndOfBarrel();
double outOfViewRadius = Math.max(getWidth(), getHeight()) * 2d;
Point2D toPoint = Util.getPointOnCircle(tank.getTurretAngle(), -90, outOfViewRadius, worldMidX, worldMidY);
Projectile projectile = new Projectile(fromPoint, toPoint);
projectiles.add(projectile);
}
});
tank.setX(200);
tank.setY(200);
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (mousePoint != null) {
double deltaX = tank.getX() - mousePoint.x;
double deltaY = tank.getY() - mousePoint.y;
// Because the image is pointing up, we need to offset
// the rotation by 90 for the API
double rotation = Math.toDegrees(Math.atan2(deltaY, deltaX) - Math.toRadians(90));
tank.setTurretAngle(rotation);
}
List<Projectile> outOfScopeProjectiles = new ArrayList<>(8);
Rectangle visibleBounds = new Rectangle(0, 0, getWidth(), getHeight());
for (Projectile projectile : projectiles) {
projectile.tick();
Point2D current = projectile.getLocation();
if (!visibleBounds.contains(current)) {
outOfScopeProjectiles.add(projectile);
}
}
projectiles.removeAll(outOfScopeProjectiles);
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
tank.paint(g2d, this);
// I don't trust the tanks paint process ;)
g2d.dispose();
g2d = (Graphics2D) g.create();
// This is all debug
if (mousePoint != null) {
// The radius around the tank based on the mouse's current location
double radius = Point2D.distance(tank.getX(), tank.getY(), mousePoint.x, mousePoint.y);
g2d.setColor(Color.MAGENTA);
g2d.draw(new Ellipse2D.Double(
tank.getX() - radius,
tank.getY() - radius,
radius * 2,
radius * 2));
// From point, base the turrets current angle
Point2D fromPoint = tank.getBusinessEndOfBarrel();
// Mid point of the tank in the world
int worldMidX = tank.getX();
int worldMidY = tank.getY();
// The point on the circle where the mouse is, based on the turrents current angle
// which the diection the turret is pointing
Point2D toPoint = Util.getPointOnCircle(tank.getTurretAngle(), -90, radius, worldMidX, worldMidY);
// The "out of view" radius, this represents the "end point" for our projectile, because, it's easier
// to calculate
double outOfViewRadius = Math.max(getWidth(), getHeight()) * 2d;
Point2D outOfViewToPoint = Util.getPointOnCircle(tank.getTurretAngle(), -90, outOfViewRadius, getWidth() / 2, getHeight() / 2);
g2d.setColor(Color.CYAN);
// The projectiles path
g2d.draw(new Line2D.Double(fromPoint, outOfViewToPoint));
// A line from the barrel to the mouse point
g2d.setColor(Color.BLUE);
g2d.draw(new Line2D.Double(fromPoint, toPoint));
}
g2d.setColor(Color.RED);
for (Projectile projectile : projectiles) {
Point2D current = projectile.getLocation();
g2d.fill(new Ellipse2D.Double(current.getX() - 2, current.getY() - 2, 4, 4));
// >>> DEBUG
g2d.draw(new Line2D.Double(projectile.getFrom(), projectile.getTo()));
// << DEBUG
}
g2d.dispose();
}
}
public class Util {
public static Point2D getPointOnCircle(double degress, double offset, double radius) {
double rads = Math.toRadians(degress + offset); // 0 becomes the top
// Calculate the outter point of the line
double xPosy = Math.cos(rads) * radius;
double yPosy = Math.sin(rads) * radius;
return new Point2D.Double(xPosy, yPosy);
}
public static Point2D getPointOnCircle(double degress, double offset, double radius, double centerX, double centerY) {
Point2D poc = getPointOnCircle(degress, offset, radius);
return new Point2D.Double(poc.getX() + centerX, poc.getY() + centerY);
}
}
public class Projectile {
private Point2D from;
private Point2D to;
private Point2D current;
private long lastUpdate = 0;
private double deltaX;
private double deltaY;
public Projectile(Point2D from, Point2D to) {
this.from = from;
this.to = to;
current = new Point2D.Double(from.getX(), from.getY());
double deltaX = from.getX() - to.getX();
double deltaY = from.getY() - to.getY();
double angle = Math.atan2(deltaY, deltaX) + Math.toRadians(180);
this.deltaY = Math.sin(angle) * 100;
this.deltaX = Math.cos(angle) * 100;
lastUpdate = System.currentTimeMillis();
}
public Point2D getFrom() {
return from;
}
public Point2D getTo() {
return to;
}
public Point2D getLocation() {
return current;
}
public void tick() {
long elapsedTime = System.currentTimeMillis() - lastUpdate;
lastUpdate = System.currentTimeMillis();
double x = current.getX();
double y = current.getY();
x += deltaX * (elapsedTime / 1000d);
y += deltaY * (elapsedTime / 1000d);
current.setLocation(x, y);
}
}
public class Tank {
private BufferedImage body;
private BufferedImage turret;
private int x;
private int y;
private double bodyAngle = 0;
private double turretAngle = 0;
private int width;
private int height;
private int midX;
private int midY;
public Tank() throws IOException {
body = ImageIO.read(getClass().getResource("/images/TankBody.png"));
turret = ImageIO.read(getClass().getResource("/images/TankTurret.png"));
width = body.getWidth();
height = body.getHeight();
midX = width / 2;
midY = height / 2;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public double getBodyAngle() {
return bodyAngle;
}
public double getTurretAngle() {
return turretAngle;
}
public void setBodyAngle(double bodyAngle) {
this.bodyAngle = bodyAngle;
}
public void setTurretAngle(double turretAngle) {
this.turretAngle = turretAngle;
}
// This represents the "unrotated" width
public int getWidth() {
return width;
}
// This represents the "unrotated" height
public int getHeight() {
return height;
}
protected int getMidX() {
return midX;
}
protected int getMidY() {
return midY;
}
protected BufferedImage getBody() {
return body;
}
protected BufferedImage getTurret() {
return turret;
}
public Point2D getBusinessEndOfBarrel() {
// I've deliberatly set up the images to be the same size, so this
// can be made easier. If your turren is a different size/position
// then you will need calculate this yourself
// Also, we're calculating this in "world" space
int centerX = getX();
int centerY = getY();
return Util.getPointOnCircle(getTurretAngle(), -90, Math.max(getWidth(), getHeight()) / 2, centerX, centerY);
}
public void paint(Graphics2D master, ImageObserver observer) {
Graphics2D g2d = (Graphics2D) master.create();
g2d.translate(getX() - getMidX(), getY() - getMidY());
g2d.setColor(Color.RED);
g2d.drawOval(0, 0, getWidth(), getHeight());
Graphics2D bodyG = (Graphics2D) g2d.create();
bodyG.rotate(Math.toRadians(getBodyAngle()), getMidX(), getMidY());
// >>> Debug
bodyG.setColor(Color.ORANGE.darker());
bodyG.drawRect(0, 0, 64, 64);
// <<< Debug
bodyG.drawImage(getBody(), 0, 0, observer);
bodyG.dispose();
Graphics2D turrtG = (Graphics2D) g2d.create();
turrtG.rotate(Math.toRadians(getTurretAngle()), getMidX(), getMidY());
// >>> Debug
turrtG.setColor(Color.GREEN.darker());
// I mesured the turrent size in a image editor
// The acutal image size is the same as the body
// in order to make the workflow simpler
turrtG.drawRect((getWidth() - 20) / 2, 0, 20, 44);
// <<< Debug
turrtG.drawImage(getTurret(), 0, 0, observer);
turrtG.dispose();
g2d.dispose();
}
}
}
I am trying to rotate a circle around a separate point in a program. right now I can get the circle to rotate but it slowly starts getting closer and closer to the point it's rotating from. I am trying to do this using JPanel and implementing it as a rectangle.
package WoffindenZone;
import java.awt.*;
import javax.swing.*;
import java.util.*;
import java.awt.event.*;
import java.lang.Math;
public class Protector extends Rectangle{
double Velocity;
int speed = 3;
Protector(int x, int y, int PROTECTOR_DIAMETER){
super(x,y,PROTECTOR_DIAMETER,PROTECTOR_DIAMETER);
}
public void keyPressed(KeyEvent e){
if(e.getKeyCode()==KeyEvent.VK_A) {
setDirection(speed);
move();
}
if(e.getKeyCode()==KeyEvent.VK_D) {
setDirection(speed);
move();
}
}
public void keyReleased(KeyEvent e){
if(e.getKeyCode()==KeyEvent.VK_A) {
setDirection(0);
move();
}
if(e.getKeyCode()==KeyEvent.VK_D) {
setDirection(0);
move();
}
}
public void setDirection(int Direction){
Velocity = Direction*Math.PI/180;
}
public void move(){
x = (int)Math.round(500 + Math.cos(Velocity) * (x-500) - Math.sin(Velocity) * (y-((1000*0.5555)/2)));
y = (int)Math.round(((1000*0.5555)/2) + Math.sin(Velocity) * (x-500) + Math.cos(Velocity) * (y-((1000*0.5555)/2)));
System.out.println(x);
System.out.println(y);
}
public void draw(Graphics g){
g.setColor(Color.blue);
g.fillOval(x,y,width,height);
}
Use a rotation instance of an AffineTransform. See getRotateInstance(theta,anchorx,anchory) for details.
Returns a transform that rotates coordinates around an anchor point. This operation is equivalent to translating the coordinates so that the anchor point is at the origin (S1), then rotating them about the new origin (S2), and finally translating so that the intermediate origin is restored to the coordinates of the original anchor point (S3).
How do you rotate a circle about a point in JPanel?
Here's how I rotate a circle about a point in a JPanel.
I don't know how to make an animated GIF. Just imagine the blue circle rotating clockwise around the center of the drawing JPanel.
So, let's start at the beginning. Basically, I have a circle rotating on the circumference of another circle. So, I create a Circle model class from plain Java.
public class Circle {
private final int radius;
private final Color color;
private Point center;
public Circle(int radius, Color color) {
this.radius = radius;
this.color = color;
}
public Point calculateCircumferencePoint(int theta) {
double radians = Math.toRadians(theta);
int x = center.x + (int) Math.round(Math.cos(radians) * radius);
int y = center.y + (int) Math.round(Math.sin(radians) * radius);
return new Point(x, y);
}
public void setCenter(int x, int y) {
this.center = new Point(x, y);
}
public void setCenter(Point center) {
this.center = center;
}
public int getRadius() {
return radius;
}
public Color getColor() {
return color;
}
public Point getCenter() {
return center;
}
}
The class consists of basic getters and setters. I make the radius and color final because they don't change value in this Java application.
The calculateCircumferencePoint method is the only interesting method. It takes an int angle in degrees, and calculates the point on the circumference represented by that angle, rounded to the nearest X and Y integer points.
Next, we create two Circle instances, an inner circle and an outer circle. Here's the class constructor setting the preferred size of the drawing area, the inner circle, and the outer circle. We start the outer circle at zero degrees (to the right);
private Circle innerCircle;
private Circle outerCircle;
private Dimension drawingPanelSize;
public RotateCircle() {
this.drawingPanelSize = new Dimension(400, 400);
int innerCircleRadius = drawingPanelSize.width / 4;
int centerX = drawingPanelSize.width / 2;
int centerY = drawingPanelSize.height / 2;
int outerCircleRadius = drawingPanelSize.width / 10;
this.innerCircle = new Circle(innerCircleRadius, null);
this.innerCircle.setCenter(centerX, centerY);
this.outerCircle = new Circle(outerCircleRadius, Color.BLUE);
Point point = innerCircle.calculateCircumferencePoint(0);
this.outerCircle.setCenter(point);
}
Now, we can start coding the GUI. First, we start the Java application by calling the SwingUtilities invokeLater method. This method ensures that we create and execute the Swing components on the Event Dispatch Thread.
Next, we define the JFrame. Here's the code we have so far.
public static void main(String[] args) {
SwingUtilities.invokeLater(new RotateCircle());
}
private Animation animation;
private Circle innerCircle;
private Circle outerCircle;
private DrawingPanel drawingPanel;
private Dimension drawingPanelSize;
public RotateCircle() {
this.drawingPanelSize = new Dimension(400, 400);
int innerCircleRadius = drawingPanelSize.width / 4;
int centerX = drawingPanelSize.width / 2;
int centerY = drawingPanelSize.height / 2;
int outerCircleRadius = drawingPanelSize.width / 10;
this.innerCircle = new Circle(innerCircleRadius, null);
this.innerCircle.setCenter(centerX, centerY);
this.outerCircle = new Circle(outerCircleRadius, Color.BLUE);
Point point = innerCircle.calculateCircumferencePoint(0);
this.outerCircle.setCenter(point);
}
#Override
public void run() {
JFrame frame = new JFrame("Rotate Circle");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
drawingPanel = new DrawingPanel(drawingPanelSize,
outerCircle);
frame.add(drawingPanel, BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
animation = new Animation(0);
new Thread(animation).start();
}
The JFrame methods must be called in a certain order. This is the order I use for most of my SWwing applications.
I pack the JFrame. I don't set a JFrame size. I let the Swing layout managers set the size of my JFrame. The default layout of a JFrame content pane is a BorderLayout. I put my drawing JPanel in the center of the BorderLayout.
Next, I create the drawing JPanel.
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = 1L;
private Circle circle;
public DrawingPanel(Dimension size, Circle circle) {
this.circle = circle;
this.setBackground(Color.WHITE);
this.setPreferredSize(size);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
Point center = circle.getCenter();
int radius = circle.getRadius();
int diameter = radius + radius;
g2d.setColor(circle.getColor());
g2d.fillOval(center.x - radius, center.y - radius,
diameter, diameter);
}
}
All the drawing JPanel does is paint a Circle object. Pretty straightforward.
The fillOval method paints an oval from the upper left hand corner. We calculate the upper left hand point from the center point.
The responsibility for calculating and updating the outer circle center point falls to my controller class, the Animation class. I use a simple loop to update the theta angle, calculate the new outer circle center point, paint the outer circle, and wait a period of time.
Here's that code.
public class Animation implements Runnable {
private int theta;
public Animation(int theta) {
this.theta = theta;
}
#Override
public void run() {
while (true) {
theta++;
theta = (theta >= 360) ? 0 : theta;
Point center = innerCircle.calculateCircumferencePoint(theta);
outerCircle.setCenter(center);
repaint();
sleep(30L);
}
}
private void repaint() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
drawingPanel.repaint();
}
});
}
private void sleep(long duration) {
try {
Thread.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
The Animation repaint method makes the call to the drawing JPanel repaint method inside another SwingUtilities invokeLater method. This method ensures that the drawing happens on the Event Dispatch Thread.
Finally, here's the complete, runnable, example. I used inner classes so I could post the code as one block, and you can copy and run this code as one block. Generally, classes should be in separate files and for a more complex GUI, separate packages.
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class RotateCircle implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new RotateCircle());
}
private Animation animation;
private Circle innerCircle;
private Circle outerCircle;
private DrawingPanel drawingPanel;
private Dimension drawingPanelSize;
public RotateCircle() {
this.drawingPanelSize = new Dimension(400, 400);
int innerCircleRadius = drawingPanelSize.width / 4;
int centerX = drawingPanelSize.width / 2;
int centerY = drawingPanelSize.height / 2;
int outerCircleRadius = drawingPanelSize.width / 10;
this.innerCircle = new Circle(innerCircleRadius, null);
this.innerCircle.setCenter(centerX, centerY);
this.outerCircle = new Circle(outerCircleRadius, Color.BLUE);
Point point = innerCircle.calculateCircumferencePoint(0);
this.outerCircle.setCenter(point);
}
#Override
public void run() {
JFrame frame = new JFrame("Rotate Circle");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
drawingPanel = new DrawingPanel(drawingPanelSize,
outerCircle);
frame.add(drawingPanel, BorderLayout.CENTER);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
animation = new Animation(0);
new Thread(animation).start();
}
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = 1L;
private Circle circle;
public DrawingPanel(Dimension size, Circle circle) {
this.circle = circle;
this.setBackground(Color.WHITE);
this.setPreferredSize(size);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
Point center = circle.getCenter();
int radius = circle.getRadius();
int diameter = radius + radius;
g2d.setColor(circle.getColor());
g2d.fillOval(center.x - radius, center.y - radius,
diameter, diameter);
}
}
public class Animation implements Runnable {
private int theta;
public Animation(int theta) {
this.theta = theta;
}
#Override
public void run() {
while (true) {
theta++;
theta = (theta >= 360) ? 0 : theta;
Point center = innerCircle.calculateCircumferencePoint(theta);
outerCircle.setCenter(center);
repaint();
sleep(30L);
}
}
private void repaint() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
drawingPanel.repaint();
}
});
}
private void sleep(long duration) {
try {
Thread.sleep(duration);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Circle {
private final int radius;
private final Color color;
private Point center;
public Circle(int radius, Color color) {
this.radius = radius;
this.color = color;
}
public Point calculateCircumferencePoint(int theta) {
double radians = Math.toRadians(theta);
int x = center.x + (int) Math.round(Math.cos(radians) * radius);
int y = center.y + (int) Math.round(Math.sin(radians) * radius);
return new Point(x, y);
}
public void setCenter(int x, int y) {
this.center = new Point(x, y);
}
public void setCenter(Point center) {
this.center = center;
}
public int getRadius() {
return radius;
}
public Color getColor() {
return color;
}
public Point getCenter() {
return center;
}
}
}
I tried to plot a circle using the java awt, what I get in output is just few small which are separated by much distance apart and doesn't look like a circle as a whole. Code is given below :
class DrawFrame extends JFrame {
int ั
ั, yc, r, x, y;
float p;
DrawFrame(int rr, int c1, int c2) {
setSize(1000, 1000);
setTitle("circle drawing algo");
r = rr;
xc = c1;
yc = c2;
}
public void paint(Graphics g) {
Circl(g);
}
public void Circl(Graphics g) {
x = xc - r;
while (x <= (xc + r)) {
for (y = yc - r; y <= (yc + r); y++) {
p = x * x + y * y - r * r;
if (p == 0.0)
g.drawOval(x, y, 2, 2);
}
x++;
}
}
You should start by having a read of Performing Custom Painting and Painting in AWT and Swing to get a better understanding of how the painting system works and how you should work with it.
You shouldn't override the paint method of top level components, apart from not been double buffered, they are actually compound components. This means that they have a number of additional components laid out on top of them which provide the overall functionality of the window.
This means that it's possible for what you've painted to the surface of the frame to be ignored, as the content on top of it paints over it.
You're also ignoring the complexity of the painting process, unless you're prepared to take over the responsibility of the paint method yourself, you should always call its super method.
A better place to start is with a JPanel (which is simpler) and override its paintComponent method
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane(100, 100, 100));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
int xc, yc, r;
public TestPane(int rr, int c1, int c2) {
r = rr;
xc = c1;
yc = c2;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(r * 2, r * 2);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
circle(g);
}
public void circle(Graphics g) {
// Credit to LAD for the algorithm updates
int x = xc - r;
while (x <= (xc + r)) {
for (int y = yc - r; y <= (yc + r); y++) {
float p = (x - xc) * (x - xc) + (y - yc) * (y - yc) - (r * r);
if (p <= 0.0f)
{
g.drawOval(x, y, 2, 2);
}
}
x++;
}
}
}
}
Credit to LAD for the algorithm updates
The first thing to change is to make JFrame visible by adding
setVisible(true); to its constructor.
It is recommended to use names that have a clear meaning to make the code more readable. Make the fields scope as limited as possible, in this case make them private:
private int ัenterX, centerY, radius;
(x,y and p are method variables and do not need to be fields )
Avoid using magic numbers. Use constants instead:
private static final int W = 1000, H = 1000, DOT_SIZE =2 ;
Putting it together, using correct Java naming conventions and fixing the algorithm:
import java.awt.Graphics; //add imports to make tour code mcve
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
class DrawFrame extends JFrame {
private final int ัenterX, centerY, radius;
private static final int W = 1000, H = 1000, DOT_SIZE =2 ;
DrawFrame(int radius, int centerX, int centerY) {
setSize(W, H);
setTitle("circle drawing algo");
this.radius = radius;
ัenterX = centerX;
this.centerY = centerY;
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//make program stop when closing frame
setVisible(true); //make frame visible
}
#Override
public void paint(Graphics g) {
super.paint(g);
circl(g);
}
public void circl(Graphics g) {
int x, y;
x = ัenterX - radius;
while (x <= ัenterX + radius) {
//calculate x
y = (int)Math.sqrt(radius*radius - (ัenterX -x)*(ัenterX -x ));
g.drawOval(x, centerY-y, 2, 2); // 2 y values for every x
g.drawOval(x, centerY+y, 2, 2);
x++;
}
}
// add main to make your code mcve
public static void main(String[] args) {
SwingUtilities.invokeLater(()-> new DrawFrame(100, 400, 400));
}
}
The next improvement could be to refactor so the painting is done on a JPanel rather than on the JFrame itself, using Graphics.drawOval:
class DrawFrame extends JFrame {
DrawFrame(int radius, int centerX, int centerY) {
setTitle("circle drawing algo");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//make program stop when closing frame
add(new DrawingPane(radius, centerX, centerY));
pack();
setVisible(true); //make frame visible
}
// add main to make your code mcve
public static void main(String[] args) {
SwingUtilities.invokeLater(()-> new DrawFrame(100, 400, 400));
}
}
class DrawingPane extends JPanel{
private final int ัenterX, centerY, radius;
private static final int W = 1000, H = 1000, DOT_SIZE =2 ;
DrawingPane(int radius, int centerX, int centerY) {
setPreferredSize(new Dimension(W, H));
this.radius = radius;
ัenterX = centerX;
this.centerY = centerY;
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
g.drawOval(ัenterX, centerY, radius, radius);
}
}
I edited your code a bit and changed the draw algorithm a little in order to completely draw the circle. Here is the refactored code:
class DrawFrame extends JFrame {
int xc, yc, r, x, y;
float p;
DrawFrame(int rr, int c1, int c2) {
setSize(1000, 1000);
setTitle("circle drawing algo");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // Handles the window being closed
setVisible(true); // Makes the window visible
r = rr;
xc = c1;
yc = c2;
}
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D)g;
GradientPaint gp = new GradientPaint(0f,0f,Color.blue,0f,30f,Color.green); // Just sets a color for the paint
g2.setPaint(gp);
Circl(g2);
}
public void Circl(Graphics g) {
x = xc-r;
while (x <= (xc+r)) {
for (y = yc-r; y <= (yc+r); y++) {
p = (x-xc)*(x-xc)+(y-yc)*(y-yc)-(r*r); // Edited this line so that itโs now the correct circle formula
if (p <= 0.0f) // If the point is 0 or less, then the point is within the circle bounds
g.drawOval(x, y, 2, 2);
}
x++;
}
}
public static void main(String[] args) {
new DrawFrame(100, 500, 500);
}
}
The code is a bit slow, though, as the drawOval method seems to be quite slow when called a bunch of times. So, this code is mainly suited for the algorithm aspect, as you could easily fill out an oval by calling the following once:
g.drawOval(x, y, 200, 200);
g.fillOval(x, y, 200, 200);
You set initial value of x as x = xc - r, y as y = yc - r. And
converting Cartesian to Polar coordinate like p = x * x + y * y - r *
r . Assume that if xc=500, and yc=500, r=50, "p" will never be 0. So
I think you forgot to calculate xc, yc when you draw. I modified your
code little bit and attached the result.
package testProject;
import java.awt.*;
import javax.swing.*;
public class DrawFrame extends JPanel {
int xc=500, yc=500, r=150, x, y;
int real_x, real_y;
float p;
public static void main(String[] args) {
JFrame.setDefaultLookAndFeelDecorated(true);
JFrame frame = new JFrame("circle drawing algo");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setBackground(Color.white);
frame.setSize(1000, 1000);
DrawFrame panel = new DrawFrame();
frame.add(panel);
frame.setVisible(true);
}
public void paint(Graphics g) {
Circl(g);
}
public void Circl(Graphics g) {
x = -r;
while (x <= r) {
y = -r;
while (y <= r) {
p = x * x + y * y - r * r;
// here is the change
if (p>=0 && p<= xc) {
g.drawOval(x+xc, y+yc, 3, 3);
}
y++;
}
x++;
}
}
}
I'm new to java. I am like to create a simple animation with a ball moving from left to right and i wish to give it an circular frame illusion(i don't know how to phrase it) If part of the ball leave the border of the frame(right side), that part would reappear in the left side of my frame. I had no idea how to start. So far i only manage to get the ball move.
This is a part of my code
public class BallAnimation extends JFrame {
private JButton left,right,up,down;
private Balls ball = new Balls();
private Ellipse2D circle;
public BallAnimation()
{
add(ball);
}
public class Balls extends JPanel{
private double X=0,Y=0;
private int i=1;
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g.setColor(Color.CYAN);
g.fillRect(0, 0, getWidth(), (getHeight()));
if(X==0 && Y ==0)
{
X=getWidth()/2.1;
Y=getHeight()/2.3;
g.setColor(Color.RED);
circle= new Ellipse2D.Double(X,Y,50,50);
g2.fill(circle);
}
else
{
g.setColor(Color.RED);
circle= new Ellipse2D.Double(X,Y,50,50);
g2.fill(circle);
}
if(i==1)
{
X=X+10;
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
repaint();
}
}
}
public static void main(String[] args)
{
BallAnimation test = new BallAnimation();
test.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
test.setSize(700,500);
test.setVisible(true);
}
}
I'd search around and didn't find anything, maybe the phrase im use is not right. Any help would be appreciated.
You can do a simple calculation to check if the ball has gone past the edge of the component.Eg.
Rectangle rect = circle.getBounds();
Rectangel bounds = getBounds();
if(rect.getX() + rect.getWidth()>bounds.getWidth()){
//draw an ellipse that is offset by the width of the component.
Ellipse2D c2 = new Ellipse2D.Double(circle.getX()-bounds.getWidth(), circle.getY(), circle.getWidth(), circle.getHeight());
g2.fill(c2);
}
There you go, if it is out of bounds on the right side it will be drawn on the left side.
Here is an example, instead of checking if the ball wraps. I just drew the ball 9 times so that when it is across the boundary it gets drawn.
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.lang.reflect.InvocationTargetException;
/**
* Created by matt on 11/30/15.
*/
public class BouncingBall {
int width = 600;
int height = 600;
double diameter = 100;
double v = 10;
double vx = 6;
double vy = 8;
Ellipse2D ball = new Ellipse2D.Double(width/4, height/4, diameter, diameter);
Ellipse2D post = new Ellipse2D.Double(width/2 - diameter, height/2 - diameter, 2*diameter, 2*diameter);
JPanel display;
void buildGui(){
JFrame frame = new JFrame("ball");
display = new JPanel(){
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
g2d.setColor(Color.BLACK);
g2d.fill(post);
g2d.setColor(Color.RED);
AffineTransform original = g2d.getTransform();
for(int i = 0; i<3; i++){
for(int j = 0; j<3; j++){
AffineTransform wrapper = new AffineTransform(original);
wrapper.translate((i-1)*width, (j-1)*height);
g2d.setTransform(wrapper);
g2d.draw(ball);
}
}
g2d.setTransform(original);
}
};
display.setMinimumSize(new Dimension(width, height));
display.setPreferredSize(new Dimension(width, height));
display.setMaximumSize(new Dimension(width, height));
frame.setContentPane(display);
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
void startSimulation(){
while (!Thread.interrupted()&&display.isVisible()) {
double x = ball.getX() + vx;
double y = ball.getY() + vy;
//wrap the ball around.
if(x>width) x = x-width;
if(x<0) x = x+width;
if(y>height) y = y-height;
if(y<0) y = y+height;
ball.setFrame(x, y, diameter, diameter);
//check for collision.
double dx = ball.getCenterX() - 0.5*width;
double dy = ball.getCenterY() - 0.5*height;
double distance = Math.sqrt(dx*dx + dy*dy);
if(distance < 1.5*diameter){
vx = v*dx/distance;
vy = v*dy/distance;
}
display.repaint();
try {
Thread.sleep(33);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
public static void main(String[] args) throws Exception {
BouncingBall b = new BouncingBall();
EventQueue.invokeAndWait(b::buildGui);
b.startSimulation();
}
}
I recently saw this question on how to rotate an image in Java. I copy/pasted it directly from that answer. On implementation, it seems to only rotate images that are squares(that is have the same size, width and height). When I try to use it for a non-square image, it seems to cut off that part that would make it be a rectangle, if that makes sense. Like this
How can I fix/work around this?
Edit: The code I'm using. Also, I won't have a scroll bar as this will be a "game", and also won't be in full screen all of the time.
public class Player extends Entity { //Entity has basic values such as (float) x & y values, along with some getters and setters
double theta;
Reticle reticle; //draws a reticle where the cursor was(basically just replaces java.awt.Cursor due to something not neccessary for me to get into)
Sprite currentImage; //basically just a BufferedImage that you can apply aspect ratios to
//constructor
#Override
public void tick() {
//(this line) gets the Reticle from the main-method class and set it to this reticle object
reticleX = reticle.getX(); //basically gets the mouse coordinates
reticleY = reticle.getY();
x += dX; //delta or change in X
y += dY //delta or change in Y
checkCollision(); //bounds checking
//getCentralizedX()/Y() gets the center of the currentImage
theta = getAngle(getCentralizedX(), getCentralizedY(), reticleX, reticleY);
currentImage = Images.rotateSprite(currentImage, theta);
}
#Override
public void render(Graphics g) {
currentImage.render(g, x, y);
g.drawLine((int) getCentralizedX(), (int) getCentralizedY(), (int) reticleX, (int) reticleY);
}
public double getAngle(float startX, float startY, float goalX, float goalY) {
double angle = Math.atan2(goalY - startY, goalX - startX);
//if(angle < 0) { //removed this as this was the thing messing up the rotation
//angle += 360;
//}
}
If the angle of the soldier is from 90 < angle < 270, then it is (basically), however, if its its 90 > angle > 270, then it gets a little wonky. Here are some pictures. It is not the angle of the aim-line(the blue line) that is wrong.
Removed all of the images as removing the if(angle < 0) inside of getAngle() fixed the rotation bug. Now the only problem is that it doesn't rotate in place.
EDIT 2: My SSCCE, which uses the same method as my game, but freaks out for some reason.
public class RotateEx extends Canvas implements Runnable {
Player player;
public RotateEx(BufferedImage image) {
player = new Player(image, 50, 50);
setPreferredSize(new Dimension(600, 600));
}
public void setDegrees(int degrees) {
player.theta = Math.toRadians(degrees);
}
public BufferedImage rotateImage(BufferedImage original, double theta) {
double cos = Math.abs(Math.cos(theta));
double sin = Math.abs(Math.sin(theta));
double width = original.getWidth();
double height = original.getHeight();
int w = (int) (width * cos + height * sin);
int h = (int) (width * sin + height * cos);
BufferedImage out = new BufferedImage(w, h, original.getType());
Graphics2D g2 = out.createGraphics();
double x = w / 2; //the middle of the two new values
double y = h / 2;
AffineTransform at = AffineTransform.getRotateInstance(theta, x, y);
x = (w - width) / 2;
y = (h - height) / 2;
at.translate(x, y);
g2.drawRenderedImage(original, at);
g2.dispose();
return out;
}
public void tick() {
player.tick();
}
public void render() {
BufferStrategy bs = this.getBufferStrategy();
if(bs == null) {
createBufferStrategy(4);
return;
}
Graphics g = bs.getDrawGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
player.render(g);
g.dispose();
bs.show();
}
public static void main(String args[]) throws IOException, InterruptedException {
String loc = "FILELOCATION"; //of course this would be a valid image file
BufferedImage image = ImageIO.read(new File(loc));
final RotateEx ex = new RotateEx(image);
final JSlider slider = new JSlider(JSlider.HORIZONTAL, 0, 360, 0);
slider.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
int value = slider.getValue();
ex.setDegrees(value);
}
});
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(ex);
f.add(slider, BorderLayout.SOUTH);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
new Thread(ex).start();
}
#Override
public void run() {
long lastTime = System.nanoTime();
final double numTicks = 60.0;
double n = 1000000000 / numTicks;
double delta = 0;
int frames = 0;
int ticks = 0;
long timer = System.currentTimeMillis();
while (true) {
long currentTime = System.nanoTime();
delta += (currentTime - lastTime) / n;
lastTime = currentTime;
render();
tick();
frames++;
if (delta >= 1) {
ticks++;
delta--;
}
}
}
class Player {
public float x, y;
int width, height;
public double theta; //how much to rotate, in radians
BufferedImage currentImage; //this image would change, according to the animation and what frame its on
public Player(BufferedImage image, float x, float y) {
this.x = x;
this.y = y;
width = image.getWidth();
height = image.getHeight();
currentImage = image;
}
public void tick() {
currentImage = rotateImage(currentImage, theta);
}
public void render(Graphics g) {
g.drawImage(currentImage, (int) x, (int) y, null);
}
}
}
When you rotate an image the width and height also change and your code doesn't take this into account.
Here is some old code I have lying around that should work better:
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.*;
public class Rotation
{
BufferedImage image;
JLabel label;
public Rotation(BufferedImage image)
{
this.image = image;
}
private BufferedImage getImage(double theta)
{
// Determine the size of the rotated image
double cos = Math.abs(Math.cos(theta));
double sin = Math.abs(Math.sin(theta));
double width = image.getWidth();
double height = image.getHeight();
int w = (int)(width * cos + height * sin);
int h = (int)(width * sin + height * cos);
// Rotate and paint the original image onto a BufferedImage
BufferedImage out = new BufferedImage(w, h, image.getType());
Graphics2D g2 = out.createGraphics();
g2.setPaint(UIManager.getColor("Panel.background"));
g2.fillRect(0,0,w,h);
double x = w/2;
double y = h/2;
AffineTransform at = AffineTransform.getRotateInstance(theta, x, y);
x = (w - width)/2;
y = (h - height)/2;
at.translate(x, y);
g2.drawRenderedImage(image, at);
g2.dispose();
return out;
}
private JLabel getLabel()
{
ImageIcon icon = new ImageIcon(image);
label = new JLabel(icon);
label.setHorizontalAlignment(JLabel.CENTER);
return label;
}
private JSlider getSlider()
{
final JSlider slider = new JSlider(JSlider.HORIZONTAL, 0, 360, 0);
slider.addChangeListener(new ChangeListener()
{
public void stateChanged(ChangeEvent e)
{
int value = slider.getValue();
BufferedImage bi = getImage(Math.toRadians(value));
label.setIcon(new ImageIcon(bi));
}
});
return slider;
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
try
{
String path = "mong.jpg";
ClassLoader cl = Rotation.class.getClassLoader();
BufferedImage bi = ImageIO.read(cl.getResourceAsStream(path));
Rotation r = new Rotation(bi);
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(new JScrollPane(r.getLabel()));
f.getContentPane().add(r.getSlider(), "South");
f.pack();
f.setLocation(200,200);
f.setVisible(true);
}
catch(IOException e)
{
System.out.println(e);
}
}
});
}
}
Edit:
Another option is to create an Icon, then you can use the Rotated Icon. Then you can rotate and paint the icon in your painting code. Something like:
import java.awt.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.*;
public class Rotation3 extends JPanel
{
private Icon icon;
private Icon rotated;
private int degrees;
public Rotation3(BufferedImage image)
{
icon = new ImageIcon( image );
setDegrees( 0 );
setPreferredSize( new Dimension(600, 600) );
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
double radians = Math.toRadians( degrees );
rotated = new RotatedIcon(icon, degrees);
// translate x/y so Icon is rotated around a specific point (300, 300)
int x = 300 - (rotated.getIconWidth() / 2);
int y = 300 - (rotated.getIconHeight() / 2);
rotated.paintIcon(this, g, x, y);
g.setColor(Color.RED);
g.fillOval(295, 295, 10, 10);
}
public void setDegrees(int degrees)
{
this.degrees = degrees;
double radians = Math.toRadians( degrees );
rotated = new RotatedIcon(icon, degrees);
repaint();
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
try
{
String path = "dukewavered.gif";
ClassLoader cl = Rotation3.class.getClassLoader();
BufferedImage bi = ImageIO.read(cl.getResourceAsStream(path));
final Rotation3 r = new Rotation3(bi);
final JSlider slider = new JSlider(JSlider.HORIZONTAL, 0, 360, 0);
slider.addChangeListener(new ChangeListener()
{
public void stateChanged(ChangeEvent e)
{
int value = slider.getValue();
r.setDegrees( value );
}
});
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new JScrollPane(r));
f.add(slider, BorderLayout.SOUTH);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
catch(IOException e)
{
System.out.println(e);
}
}
});
}
}
Edit 2:
Even easier than I thought here is an example:
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.event.*;
public class Rotation2 extends JPanel
{
BufferedImage image;
int degrees;
int point = 250;
public Rotation2(BufferedImage image)
{
this.image = image;
setDegrees( 0 );
setPreferredSize( new Dimension(600, 600) );
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g.create();
double radians = Math.toRadians( degrees );
g2.translate(point, point);
g2.rotate(radians);
g2.translate(-image.getWidth(this) / 2, -image.getHeight(this) / 2);
g2.drawImage(image, 0, 0, null);
g2.dispose();
g.setColor(Color.RED);
g.fillOval(point - 5, point - 5, 10, 10);
}
public void setDegrees(int degrees)
{
this.degrees = degrees;
repaint();
}
public static void main(String[] args)
{
EventQueue.invokeLater(new Runnable()
{
public void run()
{
try
{
// String path = "mong.jpg";
String path = "dukewavered.gif";
ClassLoader cl = Rotation2.class.getClassLoader();
BufferedImage bi = ImageIO.read(cl.getResourceAsStream(path));
final Rotation2 r = new Rotation2(bi);
final JSlider slider = new JSlider(JSlider.HORIZONTAL, 0, 360, 0);
slider.addChangeListener(new ChangeListener()
{
public void stateChanged(ChangeEvent e)
{
int value = slider.getValue();
r.setDegrees( value );
}
});
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new JScrollPane(r));
f.add(slider, BorderLayout.SOUTH);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
catch(IOException e)
{
System.out.println(e);
}
}
});
}
}
The rotation code was taken from: How to rotate an image gradually in Swing?
A far far easier way is to use the following library. Just call img.rotate(30) or whatever and it rotates, no messing about with AWT.
http://www.javaxt.com/javaxt-core/io/Image/