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'm rather new to Java Swing, and working on a ladders and snaked project for my college course. The instructor has told us to implement a game in which the player can choose exactly how many snakes are on the game board, and where the snakes are. So is for the ladders! So I cannot use one, or several fixed images in my game, so that the player cannot change them anymore.
I need a way to draw such snakes and ladders in my game. The question is what is the best option to do this in Java? By which means can I draw user-desired snakes on my game board?
One thing you could do, is rotate the image by a given angle, this way, you could still use images and supply the ability to change their start and end points
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
try {
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException exp) {
exp.printStackTrace();
}
}
});
}
public class TestPane extends JPanel {
private BufferedImage ladder;
private double angle;
public TestPane() throws IOException {
ladder = ImageIO.read(getClass().getResource("Ladder.png"));
JSlider slider = new JSlider(0, 100, 0);
slider.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
angle = (360d * (slider.getValue() / 100d));
repaint();
}
});
setLayout(new BorderLayout());
add(slider, BorderLayout.SOUTH);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
applyQualityRenderingHints(g2d);
int x = getWidth() / 2;
int y = getHeight() / 2;
g2d.setColor(Color.RED);
g2d.rotate(Math.toRadians(angle), x, y);
g2d.drawImage(ladder, x - (ladder.getWidth() / 2), y - ladder.getHeight(), this);
g2d.fillOval(x - 3, y - 3, 6, 6);
g2d.dispose();
}
protected void applyQualityRenderingHints(Graphics2D g2d) {
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);
}
}
}
Now, because you're rotating the actual Graphics context, this can become very complicated very quickly, especially when you're trying to change the location and rotate a number of objects
Another option might be to rotate the image as a whole, for example...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
try {
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException exp) {
exp.printStackTrace();
}
}
});
}
public class TestPane extends JPanel {
private BufferedImage ladder;
private double angle;
public TestPane() throws IOException {
ladder = ImageIO.read(getClass().getResource("Ladder.png"));
JSlider slider = new JSlider(0, 100, 0);
slider.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
angle = (360d * (slider.getValue() / 100d));
repaint();
}
});
setLayout(new BorderLayout());
add(slider, BorderLayout.SOUTH);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
applyQualityRenderingHints(g2d);
int x = getWidth() / 2;
int y = getHeight() / 2;
g2d.setColor(Color.RED);
BufferedImage rotated = rotate(ladder, angle);
g2d.drawImage(rotated, x - (rotated.getWidth() / 2), y - (rotated.getHeight() / 2), this);
g2d.dispose();
}
public BufferedImage rotate(BufferedImage image, double byAngle) {
double rads = Math.toRadians(byAngle);
double sin = Math.abs(Math.sin(rads)), cos = Math.abs(Math.cos(rads));
int w = image.getWidth();
int h = image.getHeight();
int newWidth = (int) Math.floor(w * cos + h * sin);
int newHeight = (int) Math.floor(h * cos + w * sin);
BufferedImage rotated = new BufferedImage(newWidth, newHeight, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = rotated.createGraphics();
applyQualityRenderingHints(g2d);
AffineTransform at = new AffineTransform();
at.translate((newWidth - w) / 2, (newHeight - h) / 2);
int x = w / 2;
int y = h / 2;
at.rotate(Math.toRadians(byAngle), x, y);
g2d.setTransform(at);
g2d.drawImage(image, 0, 0, this);
g2d.dispose();
return rotated;
}
protected void applyQualityRenderingHints(Graphics2D g2d) {
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);
}
}
}
Either way, you've got some serious management and maths ahead of you.
You might like to take a closer look at Painting in AWT and Swing and Performing Custom Painting and 2D Graphics for more details
There are many (many, many) degrees of freedom for answering this question. In fact, one could consider it as "too broad", as it is not much more specific than "how can I paint something in Swing?" (with "something" being a snake or a ladder here). There is a reason of why a significant part of game development is not only plain programming, but also the graphics design etc.
It is not clear how much the task of your course is focussed on exactly this point. If it is a general computer science course, then there likely is no need to spend dozens of hours for making the "prettiest" game. Instead, it could be sufficient to draw plain lines between the fields that the snakes/ladders should connect. Green lines for snakes, brown lines for ladders. However, maybe the priorities are different.
Regarding this question in particular, there are, broadly speaking, two options:
Paint the snakes as images
Paint the snakes as graphical objects
MadProgrammer showed in his answer the approach of using images. They can be rotated and drawn and scaled arbitrarily. In fact, when you have an image, say of size 100x1000, then you could make it span two arbitrary points. So if you have the points (200,400) and (700,1100) on the screen, then you can compute an orientation and scaling for the image so that the top center point of your image is located at (200,400), and the bottom center point is at (700,1100) - which is likely a requirement that could appear when you want to "draw a ladder starting at one field and ending at another".
The issue that I saw regarding the snakes was that the "contents" of the image would have to depend on the start- and end point. Namely, a snake that is painted between two fields that are close to each other might have a completely different shape than one that is painted between two distant fields.
(Similarly, a ladder: The number of steps that the ladder should have would certainly depend on the distance between the fields that it connects).
So, I did some "recreational programming" here, and created a snake painting class. The difference, compared to images, is that the snakes are graphical objects - particularly, they are composed of Shape objects. The tricky part is the body of the snake: It should have some waves, and a certain thickness, and the thickness should largely be constant along the body, except for the tail part....
Again: There are many degrees of freedom, and of course, this is just a snippet, quickly written down, to see (mainly for myself) of how one could tackle this problem of "drawing a snake body".
The result is a snake where you can drag around the head and tail between arbitrary points:
Some of the degrees of freedom that I mentioned are summarized as (compile-time) variables in the Snake class. One could, for example, adjust the number of "waves" based on the distance between the head and the tail point:
But these are things that I'll leave to the real artists ;-)
The code is a bit crude and largely uncommented, but maybe someone finds it helpful nevertheless:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Path2D;
import java.awt.geom.Point2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class SnakeDrawing
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(() -> createAndShowGUI());
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(new SnakeDrawingPanel());
f.setSize(800, 800);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class Snake
{
private Point2D point0 = new Point2D.Double(100,500);
private Point2D point1 = new Point2D.Double(700,500);
double bodyWidth = 10;
int waves = 4;
double waveHeight = 0.05;
double tailStart = 0.8;
double headLength = 20;
double headWidth = 16;
double eyeRadius = 6;
double irisRadius = 3;
private Shape body;
private Shape head;
private Shape eyeR;
private Shape eyeL;
private Shape irisR;
private Shape irisL;
void setPoints(Point2D point0, Point2D point1)
{
this.point0.setLocation(point0);
this.point1.setLocation(point1);
AffineTransform at = AffineTransform.getRotateInstance(
currentAngleRad(), point0.getX(), point0.getY());
at.translate(point0.getX(), point0.getY());
createBody(at);
createHead(at);
}
void draw(Graphics2D g)
{
g.setColor(new Color(0,128,0));
g.fill(body);
g.fill(head);
g.setColor(Color.WHITE);
g.fill(eyeR);
g.fill(eyeL);
g.setColor(Color.BLACK);
g.fill(irisR);
g.fill(irisL);
}
private void createBody(AffineTransform at)
{
double distance = point1.distance(point0);
int steps = 100;
Path2D body = new Path2D.Double();
Point2D previousPoint = null;
for (int i=0; i<steps; i++)
{
double alpha = (double)i/(steps-1);
Point2D point = computeCenterPoint(alpha, distance);
if (previousPoint != null)
{
Point2D bodyPoint =
computeBodyPoint(alpha, point, previousPoint);
if (i==1)
{
body.moveTo(bodyPoint.getX(), bodyPoint.getY());
}
else
{
body.lineTo(bodyPoint.getX(), bodyPoint.getY());
}
}
previousPoint = point;
}
previousPoint = null;
for (int i=steps-1; i>=0; i--)
{
double alpha = (double)i/(steps-1);
Point2D point = computeCenterPoint(alpha, distance);
if (previousPoint != null)
{
Point2D bodyPoint =
computeBodyPoint(alpha, point, previousPoint);
body.lineTo(bodyPoint.getX(), bodyPoint.getY());
}
previousPoint = point;
}
this.body = at.createTransformedShape(body);
}
private Point2D computeBodyPoint(
double alpha, Point2D point, Point2D previousPoint)
{
double dx = point.getX() - previousPoint.getX();
double dy = point.getY() - previousPoint.getY();
double rdx = -dy;
double rdy = dx;
double d = Math.hypot(dx, dy);
double localBodyWidth = bodyWidth;
if (alpha > tailStart)
{
localBodyWidth *= (1 - (alpha - tailStart) / (1.0 - tailStart));
}
double px = point.getX() + rdx * (1.0 / d) * localBodyWidth;
double py = point.getY() + rdy * (1.0 / d) * localBodyWidth;
return new Point2D.Double(px, py);
}
private Point2D computeCenterPoint(
double alpha, double distance)
{
double r = alpha * Math.PI * 2 * waves;
double verticalScaling = 1 - (alpha * 2 - 1) * (alpha * 2 - 1);
double y = Math.sin(r) * distance * waveHeight * verticalScaling;
double x = alpha * distance;
return new Point2D.Double(x,y);
}
private void createHead(AffineTransform at)
{
Shape head = new Ellipse2D.Double(
-headLength, -headWidth,
headLength + headLength,
headWidth + headWidth);
this.head = at.createTransformedShape(head);
Shape eyeR = new Ellipse2D.Double(
-headLength * 0.5 - eyeRadius,
-headWidth * 0.6 - eyeRadius,
eyeRadius + eyeRadius,
eyeRadius + eyeRadius);
Shape eyeL = new Ellipse2D.Double(
-headLength * 0.5 - eyeRadius,
headWidth * 0.6 - eyeRadius,
eyeRadius + eyeRadius,
eyeRadius + eyeRadius);
this.eyeR = at.createTransformedShape(eyeR);
this.eyeL = at.createTransformedShape(eyeL);
Shape irisR = new Ellipse2D.Double(
-headLength * 0.4 - eyeRadius,
-headWidth * 0.6 - irisRadius,
irisRadius + irisRadius,
irisRadius + irisRadius);
Shape irisL = new Ellipse2D.Double(
-headLength * 0.4 - eyeRadius,
headWidth * 0.6 - irisRadius,
irisRadius + irisRadius,
irisRadius + irisRadius);
this.irisR = at.createTransformedShape(irisR);
this.irisL = at.createTransformedShape(irisL);
}
private double currentAngleRad()
{
double dx = point1.getX() - point0.getX();
double dy = point1.getY() - point0.getY();
double angleRad = Math.atan2(dy, dx);
return angleRad;
}
}
class SnakeDrawingPanel extends JPanel
implements MouseListener, MouseMotionListener
{
private Point2D point0 = new Point2D.Double(100,500);
private Point2D point1 = new Point2D.Double(700,500);
private Point2D draggedPoint = null;
private Snake snake = new Snake();
SnakeDrawingPanel()
{
addMouseListener(this);
addMouseMotionListener(this);
}
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
snake.setPoints(point0, point1);
snake.draw(g);
}
#Override
public void mouseDragged(MouseEvent e)
{
if (draggedPoint != null)
{
draggedPoint.setLocation(e.getPoint());
repaint();
}
}
#Override
public void mouseMoved(MouseEvent e)
{
// Nothing to do here
}
#Override
public void mouseClicked(MouseEvent e)
{
// Nothing to do here
}
#Override
public void mousePressed(MouseEvent e)
{
draggedPoint = null;
double thresholdSquared = 10*10;
if (e.getPoint().distanceSq(point0) < thresholdSquared)
{
draggedPoint = point0;
}
if (e.getPoint().distanceSq(point1) < thresholdSquared)
{
draggedPoint = point1;
}
}
#Override
public void mouseReleased(MouseEvent e)
{
draggedPoint = null;
}
#Override
public void mouseEntered(MouseEvent e)
{
// Nothing to do here
}
#Override
public void mouseExited(MouseEvent e)
{
// Nothing to do here
}
}
EDIT:
As an example / extension of the answer by MadProgrammer, here is a program that contains a method that allows you to draw an image between two given points. So, for a given ladder image, you can basically drag around the top- and bottom center point of the image:
Coincidentally, the relevant method is called drawImageBetweenPoints:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class LadderDrawing
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(() -> createAndShowGUI());
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(new LadderDrawingPanel());
f.setSize(800, 800);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class LadderDrawingPanel extends JPanel
implements MouseListener, MouseMotionListener
{
private Point2D point0 = new Point2D.Double(300,300);
private Point2D point1 = new Point2D.Double(500,700);
private Point2D draggedPoint = null;
private BufferedImage ladderImage;
LadderDrawingPanel()
{
addMouseListener(this);
addMouseMotionListener(this);
try
{
ladderImage = ImageIO.read(new File("ladder.png"));
}
catch (IOException e)
{
e.printStackTrace();
}
}
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.RED);
paintDot(g, point0, 8);
paintDot(g, point1, 8);
drawImageBetweenPoints(g, ladderImage, point0, point1);
}
private static void paintDot(Graphics2D g, Point2D p, double radius)
{
g.fill(new Ellipse2D.Double(
p.getX() - radius, p.getY() - radius,
radius + radius, radius + radius));
}
private static void drawImageBetweenPoints(
Graphics2D g, BufferedImage image, Point2D p0, Point2D p1)
{
AffineTransform at = new AffineTransform();
at.concatenate(AffineTransform.getTranslateInstance(
p0.getX(), p0.getY()));
double dx = p1.getX() - p0.getX();
double dy = p1.getY() - p0.getY();
double angleRad = Math.atan2(dy, dx) - Math.PI * 0.5;
at.concatenate(AffineTransform.getRotateInstance(angleRad));
double distance = p1.distance(p0);
double scalingY = distance / image.getHeight();
// Default: Uniform scaling
double scalingX = scalingY;
// For keeping the width of the image
//scalingX = 1.0;
// For scaling to a fixed width:
//double desiredWidth = 50;
//scalingX = desiredWidth / image.getWidth();
at.concatenate(AffineTransform.getScaleInstance(scalingX, scalingY));
at.concatenate(AffineTransform.getTranslateInstance(
-image.getWidth() * 0.5, 0));
AffineTransform oldAT = g.getTransform();
g.transform(at);
g.drawImage(image, 0, 0, null);
g.setTransform(oldAT);
}
#Override
public void mouseDragged(MouseEvent e)
{
if (draggedPoint != null)
{
draggedPoint.setLocation(e.getPoint());
repaint();
}
}
#Override
public void mouseMoved(MouseEvent e)
{
// Nothing to do here
}
#Override
public void mouseClicked(MouseEvent e)
{
// Nothing to do here
}
#Override
public void mousePressed(MouseEvent e)
{
draggedPoint = null;
double thresholdSquared = 10*10;
if (e.getPoint().distanceSq(point0) < thresholdSquared)
{
draggedPoint = point0;
}
if (e.getPoint().distanceSq(point1) < thresholdSquared)
{
draggedPoint = point1;
}
}
#Override
public void mouseReleased(MouseEvent e)
{
draggedPoint = null;
}
#Override
public void mouseEntered(MouseEvent e)
{
// Nothing to do here
}
#Override
public void mouseExited(MouseEvent e)
{
// Nothing to do here
}
}
Again, I think that a manual drawing may be more flexible (and, particularly for the ladder, not much more difficult), because you can select the number of steps of the ladder to dynamically adjust based on the distance of the points. For example:
It boils down to a bit of math for computing the positions of the bars and steps, and playing a bit with strokes and shapes:
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class LadderDrawingManual
{
public static void main(String[] args)
{
SwingUtilities.invokeLater(() -> createAndShowGUI());
}
private static void createAndShowGUI()
{
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.getContentPane().add(new LadderDrawingManualPanel());
f.setSize(800, 800);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class LadderDrawingManualPanel extends JPanel
implements MouseListener, MouseMotionListener
{
private Point2D point0 = new Point2D.Double(300,300);
private Point2D point1 = new Point2D.Double(500,700);
private Point2D draggedPoint = null;
LadderDrawingManualPanel()
{
addMouseListener(this);
addMouseMotionListener(this);
}
#Override
protected void paintComponent(Graphics gr)
{
super.paintComponent(gr);
Graphics2D g = (Graphics2D)gr;
g.setRenderingHint(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(Color.RED);
paintDot(g, point0, 8);
paintDot(g, point1, 8);
drawLadderBetweenPoints(g, point0, point1);
}
private static void paintDot(Graphics2D g, Point2D p, double radius)
{
g.fill(new Ellipse2D.Double(
p.getX() - radius, p.getY() - radius,
radius + radius, radius + radius));
}
private static void drawLadderBetweenPoints(
Graphics2D g, Point2D p0, Point2D p1)
{
final double ladderWidth = 40;
final double distanceBetweenSteps = 30;
final double barWidth = 5;
double dx = p1.getX() - p0.getX();
double dy = p1.getY() - p0.getY();
double distance = p1.distance(p0);
double dirX = dx / distance;
double dirY = dy / distance;
double offsetX = dirY * ladderWidth * 0.5;
double offsetY = -dirX * ladderWidth * 0.5;
Line2D lineR = new Line2D.Double(
p0.getX() + offsetX,
p0.getY() + offsetY,
p1.getX() + offsetX,
p1.getY() + offsetY);
Line2D lineL = new Line2D.Double(
p0.getX() - offsetX,
p0.getY() - offsetY,
p1.getX() - offsetX,
p1.getY() - offsetY);
drawBar(g, lineL, barWidth);
drawBar(g, lineR, barWidth);
int numSteps = (int)(distance / distanceBetweenSteps);
for (int i=0; i<numSteps; i++)
{
double stepOffsetX = (i+1) * distanceBetweenSteps;
double stepOffsetY = (i+1) * distanceBetweenSteps;
Line2D step = new Line2D.Double(
p0.getX() + stepOffsetX * dirX - offsetX,
p0.getY() + stepOffsetY * dirY - offsetY,
p0.getX() + stepOffsetX * dirX + offsetX,
p0.getY() + stepOffsetY * dirY + offsetY);
drawBar(g, step, barWidth);
}
}
private static void drawBar(Graphics2D g, Line2D line, double barWidth)
{
Stroke stroke = new BasicStroke(
(float)barWidth, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
Shape bar = stroke.createStrokedShape(line);
g.setColor(new Color(200,100,0));
g.fill(bar);
g.setColor(Color.BLACK);
g.draw(bar);
}
#Override
public void mouseDragged(MouseEvent e)
{
if (draggedPoint != null)
{
draggedPoint.setLocation(e.getPoint());
repaint();
}
}
#Override
public void mouseMoved(MouseEvent e)
{
// Nothing to do here
}
#Override
public void mouseClicked(MouseEvent e)
{
// Nothing to do here
}
#Override
public void mousePressed(MouseEvent e)
{
draggedPoint = null;
double thresholdSquared = 10*10;
if (e.getPoint().distanceSq(point0) < thresholdSquared)
{
draggedPoint = point0;
}
if (e.getPoint().distanceSq(point1) < thresholdSquared)
{
draggedPoint = point1;
}
}
#Override
public void mouseReleased(MouseEvent e)
{
draggedPoint = null;
}
#Override
public void mouseEntered(MouseEvent e)
{
// Nothing to do here
}
#Override
public void mouseExited(MouseEvent e)
{
// Nothing to do here
}
}
I've written a java implementation of Craig Reynolds Boids. I recently updated each object to be represented by a .png image. Ever since I've been having the display issue in the image.
What's the best way to fix the issue?
I've tried using a Polygon but when one of my coordinates is a negative the triangle doesn't display properly.
Main Class:
public void paint(final GraphicsContext g) {
new AnimationTimer() {
#Override
public void handle(long now) {
flock.updateBoidsPostion();
g.clearRect(0, 0, width, height);
flock.drawBoids(g);
}
}.start();
}
#Override
public void start(Stage primaryStage) throws Exception {
primaryStage.setTitle("Boids Flocking Algorithm");
Group root = new Group();
Canvas canvas = new Canvas(width, height);
GraphicsContext gc = canvas.getGraphicsContext2D();
root.getChildren().add(canvas);
primaryStage.setScene(new Scene(root));
primaryStage.show();
paint(gc);
}
Flock:
/**
* Paint each boid comprising the flock the canvas.
* #param g
*/
public void drawBoids(GraphicsContext g) {
for(Boid aBoid : boids) {
aBoid.draw(g);
}
}
Boid:
public void draw(GraphicsContext g) {
//coordinates for the tip of the boid
int x = (int)this.position.xPos;
int y = (int)this.position.yPos;
//Calculate a angle representing the direction of travel.
Rotate r = new Rotate(angle, x, y);
g.setTransform(r.getMxx(), r.getMyx(), r.getMxy(), r.getMyy(), r.getTx(), r.getTy());
g.drawImage(image, x, y);
}
The problem with your code is that you rotate the GraphicsContext, not the image. Or at least you don't rotate the GraphicsContext back after you rotated it.
I was curious about the link you mentioned, i. e. the Boids Pseudocode.
Here's a quick implementation. Drag the rectangle to have the flock follow it.
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.event.EventHandler;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.scene.shape.Rectangle;
import javafx.scene.text.Text;
import javafx.stage.Stage;
// Boids implementation in JavaFX
// Pseudo code by Conrad Parker: http://www.kfish.org/boids/pseudocode.html
public class Main extends Application {
int numBoids = 50;
double boidRadius = 10d;
double boidMinDistance = boidRadius * 2d + 5; // +5 = arbitrary value
double initialBaseVelocity = 1d;
double velocityLimit = 3d;
double movementToCenter = 0.01; // 1% towards center
List<Boid> boids;
static Random rnd = new Random();
double sceneWidth = 1024;
double sceneHeight = 768;
Pane playfield;
Rectangle rectangle;
#Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
playfield = new Pane();
playfield.setPrefSize(sceneWidth, sceneHeight);
Text infoText = new Text( "Drag the rectangle and have the flock follow it");
root.setTop(infoText);
root.setCenter(playfield);
Scene scene = new Scene(root, sceneWidth, sceneHeight, Color.WHITE);
//scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
// create boids
createBoids();
// add boids to scene
playfield.getChildren().addAll(boids);
double w = 20;
double h = 20;
rectangle = new Rectangle( w, h);
rectangle.relocate(sceneWidth / 2 - w/2, sceneHeight / 4 - h/2);
playfield.getChildren().add(rectangle);
MouseGestures mg = new MouseGestures();
mg.makeDraggable(rectangle);
// animation loop
AnimationTimer loop = new AnimationTimer() {
#Override
public void handle(long now) {
boids.forEach(Boid::move);
boids.forEach(Boid::updateUI);
}
};
loop.start();
}
private void createBoids() {
boids = new ArrayList<>();
// margin from top/left/bottom/right, so we have the boids initially more in the center
double marginX = sceneWidth / 4;
double marginY = sceneHeight / 4;
for (int i = 0; i < numBoids; i++) {
// random position around the center
double x = rnd.nextDouble() * (sceneWidth - marginX * 2) + marginX;
double y = rnd.nextDouble() * (sceneHeight - marginY * 2) + marginY;
// initial random velocity depending on speed
double v = Math.random() * 4 + initialBaseVelocity;
Boid boid = new Boid(i, x, y, v);
boids.add(boid);
}
}
// Rule 1: Boids try to fly towards the centre of mass of neighbouring boids.
public Point2D rule1(Boid boid) {
Point2D pcj = new Point2D(0, 0);
for( Boid neighbor: boids) {
if( boid == neighbor)
continue;
pcj = pcj.add( neighbor.position);
}
if( boids.size() > 1) {
double div = 1d / (boids.size() - 1);
pcj = pcj.multiply( div);
}
pcj = (pcj.subtract(boid.position)).multiply( movementToCenter);
return pcj;
}
// Rule 2: Boids try to keep a small distance away from other objects (including other boids).
public Point2D rule2(Boid boid) {
Point2D c = new Point2D(0, 0);
for( Boid neighbor: boids) {
if( boid == neighbor)
continue;
double distance = (neighbor.position.subtract(boid.position)).magnitude();
if( distance < boidMinDistance) {
c = c.subtract(neighbor.position.subtract(boid.position));
}
}
return c;
}
// Rule 3: Boids try to match velocity with near boids.
public Point2D rule3(Boid boid) {
Point2D pvj = new Point2D(0, 0);
for( Boid neighbor: boids) {
if( boid == neighbor)
continue;
pvj = pvj.add( neighbor.velocity);
}
if( boids.size() > 1) {
double div = 1d / (boids.size() - 1);
pvj = pvj.multiply( div);
}
pvj = (pvj.subtract(boid.velocity)).multiply(0.125); // 0.125 = 1/8
return pvj;
}
// tend towards the rectangle
public Point2D tendToPlace( Boid boid) {
Point2D place = new Point2D( rectangle.getBoundsInParent().getMinX() + rectangle.getBoundsInParent().getWidth() / 2, rectangle.getBoundsInParent().getMinY() + rectangle.getBoundsInParent().getHeight() / 2);
return (place.subtract(boid.position)).multiply( 0.01);
}
public class Boid extends Circle {
int id;
Point2D position;
Point2D velocity;
double v;
// random color
Color color = randomColor();
public Boid(int id, double x, double y, double v) {
this.id = id;
this.v = v;
position = new Point2D( x, y);
velocity = new Point2D( v, v);
setRadius( boidRadius);
setStroke(color);
setFill(color.deriveColor(1, 1, 1, 0.2));
}
public void move() {
Point2D v1 = rule1(this);
Point2D v2 = rule2(this);
Point2D v3 = rule3(this);
Point2D v4 = tendToPlace(this);
velocity = velocity
.add(v1)
.add(v2)
.add(v3)
.add(v4)
;
limitVelocity();
position = position.add(velocity);
constrainPosition();
}
private void limitVelocity() {
double vlim = velocityLimit;
if( velocity.magnitude() > vlim) {
velocity = (velocity.multiply(1d/velocity.magnitude())).multiply( vlim);
}
}
// limit position to screen dimensions
public void constrainPosition() {
double xMin = boidRadius;
double xMax = sceneWidth - boidRadius;
double yMin = boidRadius;
double yMax = sceneHeight - boidRadius;
double x = position.getX();
double y = position.getY();
double vx = velocity.getX();
double vy = velocity.getY();
if( x < xMin) {
x = xMin;
vx = v;
}
else if( x > xMax) {
x = xMax;
vx = -v;
}
if( y < yMin) {
y = yMin;
vy = v;
}
else if( y > yMax) {
y = yMax;
vy = -v;
}
// TODO: modification would be less performance consuming => find out how to modify the vector directly or create own Poin2D class
position = new Point2D( x, y);
velocity = new Point2D( vx, vy);
}
public void updateUI() {
setCenterX(position.getX());
setCenterY(position.getY());
}
}
public static Color randomColor() {
int range = 220;
return Color.rgb((int) (rnd.nextDouble() * range), (int) (rnd.nextDouble() * range), (int) (rnd.nextDouble() * range));
}
public static class MouseGestures {
class DragContext {
double x;
double y;
}
DragContext dragContext = new DragContext();
public void makeDraggable( Node node) {
node.setOnMousePressed( onMousePressedEventHandler);
node.setOnMouseDragged( onMouseDraggedEventHandler);
node.setOnMouseReleased( onMouseReleasedEventHandler);
}
EventHandler<MouseEvent> onMousePressedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if( event.getSource() instanceof Circle) {
Circle circle = ((Circle) (event.getSource()));
dragContext.x = circle.getCenterX() - event.getSceneX();
dragContext.y = circle.getCenterY() - event.getSceneY();
} else {
Node node = ((Node) (event.getSource()));
dragContext.x = node.getTranslateX() - event.getSceneX();
dragContext.y = node.getTranslateY() - event.getSceneY();
}
}
};
EventHandler<MouseEvent> onMouseDraggedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
if( event.getSource() instanceof Circle) {
Circle circle = ((Circle) (event.getSource()));
circle.setCenterX( dragContext.x + event.getSceneX());
circle.setCenterY( dragContext.y + event.getSceneY());
} else {
Node node = ((Node) (event.getSource()));
node.setTranslateX( dragContext.x + event.getSceneX());
node.setTranslateY( dragContext.y + event.getSceneY());
}
}
};
EventHandler<MouseEvent> onMouseReleasedEventHandler = new EventHandler<MouseEvent>() {
#Override
public void handle(MouseEvent event) {
}
};
}
public static void main(String[] args) {
launch(args);
}
}
A 3D version is just a matter of using Points3D instead of Points2D and Spheres and Boxes instead of Circles and Rectangles.
I also suggest you read the excellent book The Nature of Code by Daniel Shiffman, especially the chapter Autonomous Agents. It deals in detail with the Boids.
is it possible to simply make 360 degree movement in java(swing) without any game engine? all I have is this attempt:
public class Game extends JPanel implements Runnable {
int x = 300;
int y = 500;
float angle = 30;
Game game;
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.add(new Game());
frame.setSize(600, 600);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
public Game() {
setSize(600, 600);
Thread thread = new Thread(this);
thread.start();
}
#Override
public void paint(Graphics g) {
g.setColor(Color.WHITE);
g.drawRect(0, 0, 600, 600);
g.setColor(Color.CYAN);
g.fillOval(x, y, 10, 10);
g.dispose();
}
#Override
public void run() {
while(true) {
angle += -0.1;
x += Math.sin(angle);
y--;
repaint();
try {
Thread.sleep(50);
} catch (InterruptedException ex) {}
}
}
}
as you can see in following picture, I don't know how to handle movement rotating, this is the output:
image http://screenshot.cz/GOXE3/mvm.jpg
Actually, this is quite possible.
My preferred way is to actually take advantage of the Graphics transform so that you don't have to do any computation, it's all left to the Graphics
By the way:
since you did not create the Graphics object, don't ever dispose it.
override paintComponent() rather than paint()
It's always a good pattern to call super.paintComponent()
Small demo example:
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Area;
import java.awt.geom.RoundRectangle2D;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class TestRotate {
public static class ShapeAndColor {
private final Shape shape;
private final Color color;
public ShapeAndColor(Shape shape, Color color) {
super();
this.shape = shape;
this.color = color;
}
public Shape getShape() {
return shape;
}
public Color getColor() {
return color;
}
}
public static class RotatingShapesPanel extends JComponent {
private List<ShapeAndColor> shapes;
private double rotation = 0.0;
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
AffineTransform translate = AffineTransform.getTranslateInstance(-getWidth() / 2, -getHeight() / 2);
AffineTransform rotate = AffineTransform.getRotateInstance(rotation);
AffineTransform t = AffineTransform.getTranslateInstance(getWidth() / 2, getHeight() / 2);
t.concatenate(rotate);
t.concatenate(translate);
g2d.setTransform(t);
AffineTransform scale = AffineTransform.getScaleInstance(getWidth(), getHeight());
for (ShapeAndColor shape : shapes) {
Area area = new Area(shape.getShape());
g2d.setColor(shape.getColor());
area.transform(scale);
g2d.fill(area);
}
}
public void setShapes(List<ShapeAndColor> shapes) {
this.shapes = shapes;
repaint();
}
public double getRotation() {
return rotation;
}
public void setRotation(double rotation) {
this.rotation = rotation;
repaint();
}
}
protected void initUI(final boolean useBorderLayout) {
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
List<ShapeAndColor> shapes = new ArrayList<>();
Random r = new Random();
for (int i = 0; i < 10; i++) {
double x = r.nextDouble();
double y = r.nextDouble();
double w = r.nextDouble();
double h = r.nextDouble();
w = Math.min(w, 1 - x) / 2;
h = Math.min(h, 1 - y) / 2;
double a = Math.min(w, h) / 10.0;
RoundRectangle2D.Double shape = new RoundRectangle2D.Double(x, y, w, h, a, a);
Color color = new Color(r.nextInt(256), r.nextInt(256), r.nextInt(256));
shapes.add(new ShapeAndColor(shape, color));
}
final RotatingShapesPanel panel = new RotatingShapesPanel();
panel.setShapes(shapes);
frame.add(panel);
frame.setSize(600, 600);
frame.setVisible(true);
Timer t = new Timer(0, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
double rotation = panel.getRotation() + 0.02;
if (rotation > Math.PI * 2) {
rotation -= Math.PI * 2;
}
panel.setRotation(rotation);
}
});
t.setRepeats(true);
t.setDelay(10);
t.start();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TestRotate().initUI(true);
}
});
}
}
Change a few lines...
int basex = 300; // midpoint of the circle
int basey = 400;
int radius = 100; // radius
int x;
int y;
float angle = 0; // Angles in radians, NOT degrees!
public void run() {
while(true) {
angle += 0.01;
x = (int)(basex + radius*Math.cos(angle));
y = (int)(basey - radius*Math.sin(angle));
repaint();
try {
Thread.sleep(50);
} catch (InterruptedException ex) {}
}
}
Not sure what you were trying to code there, but this is the correct formula for a circular movement.
To calculate the rotation around a point, you need a center point for the rotation (cx, cy), the radius or distance of the point from the center, you need the angle (in radians, not degrees), and you need to use sine and cosine to calculate the offset of the point from the center as it rotates around it.
int cx, cy, radius; // I'll let you determine these
double theta = Math.toRadians(30);
double dtheta = Math.toRadians(-0.1);
double dx = Math.cos(theta) * radius;
double dy = Math.sin(theta) * radius;
int x = (int)(cx + dx);
int y = (int)(cy + dy);
repaint();
theta += dtheta; // step the angle
You program has some problems:
int x = 300;
int y = 500;
You should use a floating point data type like double to store the coordinates. You can cast them to int when you want to draw them. If you store them in int, you'll lose precision.
x += Math.sin(angle);
y--;
This doesn't work, since y is decremented instead of calculated using Math.sin(angle). (us Math.cos for x)
This is your fixed code (unchanged parts are omitted):
double x = 300;
double y = 500;
float angle = 30;
double radius = 10D; // new variable to increase the radius of the drawn circle
Game game;
// main method
// constructor
#Override
public void paint(Graphics g) {
// ... stuff omitted
g.fillOval((int)x, (int)y, 10, 10); // you can cast to int here
g.dispose();
}
#Override
public void run() {
while (true) {
angle -= 0.1; // is the same as `angle += -0.1`
x += radius * Math.cos(angle);
y += radius * Math.sin(angle);
repaint();
// ... try catch block
}
}
This currenlty draw the circle counter-clockwise. If you want to draw it clockwise, then change angle to:
angle += 0.1;
I am creating simple flowchart software.i want to create oval/parallelogram/diamond/rectangle shapes. I want to resize those shapes and drag those shapes.because of resizing I cant use images.I add drag events to rectangle shape japanel but I unable to create other shapes using jpanel. How to add parallelogram/diamond shapes to jpanel?
given "Resizing issue with canvas within jscrollpane within jsplitpane" question not answered for parallelogram shape and diamond shape i want to
create those shapes too
/* ResizableComponent.java */
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
/* ResizableComponent.java */
public class ResizableComponent extends JFrame {
private JPanel panel = new JPanel(null);
private Resizable resizer;
public ResizableComponent() {
add(panel);
JPanel area = new JPanel();
area.setBackground(Color.white);
resizer = new Resizable(area);
resizer.setBounds(50, 30, 200, 150);
panel.add(resizer);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(new Dimension(350, 300));
setTitle("Resizable Component");
setLocationRelativeTo(null);
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent me) {
requestFocus();
resizer.repaint();
}
});
}
public static void main(String[] args) {
ResizableComponent rc = new ResizableComponent();
rc.setVisible(true);
}
}
// Resizable.java
import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import javax.swing.JComponent;
import javax.swing.event.MouseInputAdapter;
import javax.swing.event.MouseInputListener;
// Resizable.java
public class Resizable extends JComponent {
public Resizable(Component comp) {
this(comp, new ResizableBorder(8));
}
public Resizable(Component comp, ResizableBorder border) {
setLayout(new BorderLayout());
add(comp);
addMouseListener(resizeListener);
addMouseMotionListener(resizeListener);
setBorder(border);
}
private void resize() {
if (getParent() != null) {
((JComponent)getParent()).revalidate();
}
}
MouseInputListener resizeListener = new MouseInputAdapter() {
public void mouseMoved(MouseEvent me) {
if (hasFocus()) {
ResizableBorder border = (ResizableBorder)getBorder();
setCursor(Cursor.getPredefinedCursor(border.getCursor(me)));
}
}
public void mouseExited(MouseEvent mouseEvent) {
setCursor(Cursor.getDefaultCursor());
}
private int cursor;
private Point startPos = null;
public void mousePressed(MouseEvent me) {
ResizableBorder border = (ResizableBorder)getBorder();
cursor = border.getCursor(me);
startPos = me.getPoint();
requestFocus();
repaint();
}
public void mouseDragged(MouseEvent me) {
if (startPos != null) {
int x = getX();
int y = getY();
int w = getWidth();
int h = getHeight();
int dx = me.getX() - startPos.x;
int dy = me.getY() - startPos.y;
switch (cursor) {
case Cursor.N_RESIZE_CURSOR:
if (!(h - dy < 50)) {
setBounds(x, y + dy, w, h - dy);
resize();
}
break;
case Cursor.S_RESIZE_CURSOR:
if (!(h + dy < 50)) {
setBounds(x, y, w, h + dy);
startPos = me.getPoint();
resize();
}
break;
case Cursor.W_RESIZE_CURSOR:
if (!(w - dx < 50)) {
setBounds(x + dx, y, w - dx, h);
resize();
}
break;
case Cursor.E_RESIZE_CURSOR:
if (!(w + dx < 50)) {
setBounds(x, y, w + dx, h);
startPos = me.getPoint();
resize();
}
break;
case Cursor.NW_RESIZE_CURSOR:
if (!(w - dx < 50) && !(h - dy < 50)) {
setBounds(x + dx, y + dy, w - dx, h - dy);
resize();
}
break;
case Cursor.NE_RESIZE_CURSOR:
if (!(w + dx < 50) && !(h - dy < 50)) {
setBounds(x, y + dy, w + dx, h - dy);
startPos = new Point(me.getX(), startPos.y);
resize();
}
break;
case Cursor.SW_RESIZE_CURSOR:
if (!(w - dx < 50) && !(h + dy < 50)) {
setBounds(x + dx, y, w - dx, h + dy);
startPos = new Point(startPos.x, me.getY());
resize();
}
break;
case Cursor.SE_RESIZE_CURSOR:
if (!(w + dx < 50) && !(h + dy < 50)) {
setBounds(x, y, w + dx, h + dy);
startPos = me.getPoint();
resize();
}
break;
case Cursor.MOVE_CURSOR:
Rectangle bounds = getBounds();
bounds.translate(dx, dy);
setBounds(bounds);
resize();
}
setCursor(Cursor.getPredefinedCursor(cursor));
}
}
public void mouseReleased(MouseEvent mouseEvent) {
startPos = null;
}
};
}
// ResizableBorder.java
import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.Rectangle;
import java.awt.event.MouseEvent;
import javax.swing.SwingConstants;
import javax.swing.border.Border;
// ResizableBorder.java
public class ResizableBorder implements Border {
private int dist = 8;
int locations[] =
{
SwingConstants.NORTH, SwingConstants.SOUTH, SwingConstants.WEST,
SwingConstants.EAST, SwingConstants.NORTH_WEST,
SwingConstants.NORTH_EAST, SwingConstants.SOUTH_WEST,
SwingConstants.SOUTH_EAST
};
int cursors[] =
{
Cursor.N_RESIZE_CURSOR, Cursor.S_RESIZE_CURSOR, Cursor.W_RESIZE_CURSOR,
Cursor.E_RESIZE_CURSOR, Cursor.NW_RESIZE_CURSOR, Cursor.NE_RESIZE_CURSOR,
Cursor.SW_RESIZE_CURSOR, Cursor.SE_RESIZE_CURSOR
};
public ResizableBorder(int dist) {
this.dist = dist;
}
public Insets getBorderInsets(Component component) {
return new Insets(dist, dist, dist, dist);
}
public boolean isBorderOpaque() {
return false;
}
public void paintBorder(Component component, Graphics g, int x, int y,
int w, int h) {
g.setColor(Color.black);
g.drawRect(x + dist / 2, y + dist / 2, w - dist, h - dist);
if (component.hasFocus()) {
for (int i = 0; i < locations.length; i++) {
Rectangle rect = getRectangle(x, y, w, h, locations[i]);
g.setColor(Color.WHITE);
g.fillRect(rect.x, rect.y, rect.width - 1, rect.height - 1);
g.setColor(Color.BLACK);
g.drawRect(rect.x, rect.y, rect.width - 1, rect.height - 1);
}
}
}
private Rectangle getRectangle(int x, int y, int w, int h, int location) {
switch (location) {
case SwingConstants.NORTH:
return new Rectangle(x + w / 2 - dist / 2, y, dist, dist);
case SwingConstants.SOUTH:
return new Rectangle(x + w / 2 - dist / 2, y + h - dist, dist,
dist);
case SwingConstants.WEST:
return new Rectangle(x, y + h / 2 - dist / 2, dist, dist);
case SwingConstants.EAST:
return new Rectangle(x + w - dist, y + h / 2 - dist / 2, dist,
dist);
case SwingConstants.NORTH_WEST:
return new Rectangle(x, y, dist, dist);
case SwingConstants.NORTH_EAST:
return new Rectangle(x + w - dist, y, dist, dist);
case SwingConstants.SOUTH_WEST:
return new Rectangle(x, y + h - dist, dist, dist);
case SwingConstants.SOUTH_EAST:
return new Rectangle(x + w - dist, y + h - dist, dist, dist);
}
return null;
}
public int getCursor(MouseEvent me) {
Component c = me.getComponent();
int w = c.getWidth();
int h = c.getHeight();
for (int i = 0; i < locations.length; i++) {
Rectangle rect = getRectangle(0, 0, w, h, locations[i]);
if (rect.contains(me.getPoint()))
return cursors[i];
}
return Cursor.MOVE_CURSOR;
}
}