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 currently trying to create a simple dodge game using Java Swing and AWT. Right now, I have created some simple outline code to test if the concept I am trying to create will work. I have used polymorphism to try to create multiple types of object Enemy which will have individual draw() and act() code that makes them be drawn by AWT and then move in a specific fashion based on what type they are. I imported Graphics2D to draw() in an attempt to make the code more reusable. I then used a while loop to run the Java Swing/AWT built-in thread to allow animations for the enemies. However, when I run the code, it compiles correctly, but only a blank screen is displayed.
How can I fix it?
Here is the code I used. The code involving the Mouse is incomplete.
Game.java
import javax.swing.*;
import java.awt.*;
public class Game extends JPanel {
//FPS Setup
int fps = 30; //FPS
int secConst = 1000; //milliseconds per second
int frmConst = (int) Math.floor((double) secConst / (double) fps); //delay between frames
//FRAME Setup
String appName = "Dodge This"; //app name
int frameW = 500; //frame width
int frameH = 500; //frame height
//ENEMY TEST
//TO REPLACE WITH ARRAY OF ENEMIES
Square square = new Square(0, 100, 0, 10);
Circle circle = new Circle(50, 50, 10);
boolean lose = true;
#Override
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
//TO REPLACE WITH LOOP THROUGH ARRAY OF ENEMIES
square.act();
square.draw(g2d);
circle.act();
circle.draw(g2d);
if (this.lose) {
g2d.setColor(new Color(0, 0, 0));
g2d.setFont(new Font("Sans Serif", Font.PLAIN, 32));
g2d.drawString("You Lose", 0, 0); //TO REPLACE WITH RANDOMIZED LOSE MESSAGE
}
}
public static void main(String [] args) throws InterruptedException {
Game game = new Game();
JFrame frame = new JFrame(game.appName);
frame.setSize(game.frameW, game.frameH);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
while (true) {
if (MouseInfo.getPointerInfo().getLocation().equals(new Point(game.circle.getX(), game.circle.getY())) && MouseInfo.getPointerInfo().getLocation().equals(new Point(game.square.getX(), game.square.getY()))) {
game.lose = true;
break;
}
game.repaint();
Thread.sleep(game.frmConst);
}
}
}
Enemy.java
import java.awt.*;
public abstract class Enemy {
public int x;
public int y;
public double direction;
public double speed;
//BASIC METHODS
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public void setDirection(double direction) {
this.direction = direction;
}
public void setSpeed(double speed) {
this.speed = speed;
}
public int getX() {
return this.x;
}
public int getY() {
return this.y;
}
public double getDirection() {
return this.direction;
}
public double getSpeed() {
return this.speed;
}
//METHODS FOR UNIQUE ENEMIES
public abstract void act();
public abstract void draw(Graphics2D g2d);
}
Square.java
import java.awt.*;
public class Square extends Enemy{
//CONSTRUCTORS
public Square() {
this.x = 0;
this.y = 0;
this.direction = (int) Math.floor(Math.random() * 8) * 45;
this.speed = (int) Math.floor(Math.random() * 15);
} //default constructor
public Square(int x, int y) {
this.x = x;
this.y = y;
this.direction = (int) Math.floor(Math.random() * 8) * 45;
this.speed = (int) Math.floor(Math.random() * 15);
}
public Square(int x, int y, double direction) {
this.x = x;
this.y = y;
this.direction = direction;
this.speed = (int) Math.floor(Math.random() * 15);
}
public Square(int x, int y, double direction, double speed) {
this.x = x;
this.y = y;
this.direction = direction;
this.speed = speed;
}
//ACTIONS
#Override
public void act() {
this.x += Math.floor(this.speed * (Math.cos(this.direction)));
this.y += Math.floor(this.speed * -1 * (Math.sin(this.direction)));
}
#Override
public void draw(Graphics2D g2d) {
g2d.setColor(new Color(100, 100, 100));
g2d.fillRect(this.x, this.y, 50, 50);
}
}
Circle.java
import java.awt.*;
public class Circle extends Enemy{
double mouseX;
double mouseY;
//CONSTRUCTORS
public Circle() {
this.x = 0;
this.y = 0;
this.direction = 0;
this.speed = 5;
} //default constructor
public Circle(int x, int y) {
this.x = x;
this.y = y;
this.direction = 0;
this.speed = 5;
}
public Circle (int x, int y, int speed) {
this.x = x;
this.y = y;
this.direction = 0;
this.speed = speed;
}
//ACTIONS
#Override
public void act() {
this.mouseX = MouseInfo.getPointerInfo().getLocation().getX();
this.mouseY = MouseInfo.getPointerInfo().getLocation().getY();
this.x += this.speed * Math.floor(Math.abs(this.y - this.mouseY) / Math.abs(this.x - this.mouseX));
this.y += this.speed * -1 * Math.floor(Math.abs(this.x - this.mouseX) / Math.abs(this.y - this.mouseY));
}
#Override
public void draw(Graphics2D g2d) {
g2d.setColor(new Color(50, 50, 50));
g2d.fillOval(this.x, this.y, 10, 10);
}
}
See the following code for some improvements in the program structure as well as better use of Swing tools. Note the comments:
import java.awt.*;
import javax.swing.*;
public class Game extends JPanel {
//FPS Setup
int fps = 3; // FPS (slow for testing)
int secConst = 1000; //milliseconds per second
int frmConst = secConst / fps; //delay between frames
private Timer timer;
String appName = "Dodge This"; //app name
int frameW = 500, frameH = 500; //frame width and height
Square square = new Square(0, 100, 0., 10.);
Circle circle = new Circle(50, 50, 0., 10.);
boolean lose = false; //the correct state at start ;
//override paintComponent rather than paint
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
square.draw(g2d);
circle.draw(g2d);
if (lose) {
g2d.setColor(new Color(0, 0, 0));
g2d.setFont(new Font("Sans Serif", Font.PLAIN, 32));
g2d.drawString("You Lose", 0, 0);
}
}
public void start(){
//use swing timer to invoke game cycles
if(timer != null && timer.isRunning()) {
timer.stop();
}
timer = new Timer(frmConst, e->{
if(lose) {
timer.stop();
} else {
play();
}
}
);
timer.start();
}
public void play(){
square.act();
circle.act();
//lose criteria (not sure what are you trying to check)
if (MouseInfo.getPointerInfo().getLocation().equals(new Point(circle.getX(), circle.getY()))
&& MouseInfo.getPointerInfo().getLocation().equals(new Point(square.getX(), square.getY()))) {
lose = true;
}
repaint();
}
public static void main(String [] args) throws InterruptedException {
Game game = new Game();
JFrame frame = new JFrame(game.appName);
frame.setSize(game.frameW, game.frameH);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(game);// add game to frame
game.start();
}
}
abstract class Enemy {
public int x,y;
public double direction, speed;
public Enemy(int x, int y, double speed) {
this(x,y,0, speed);
}
public Enemy(int x, int y, double direction, double speed) {
this.x = x;
this.y = y;
this.direction = direction;
this.speed = speed;
}
//BASIC METHODS
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public void setDirection(double direction) {
this.direction = direction;
}
public void setSpeed(double speed) {
this.speed = speed;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public double getDirection() {
return direction;
}
public double getSpeed() {
return speed;
}
//METHODS FOR UNIQUE ENEMIES
public abstract void act();
public abstract void draw(Graphics2D g2d);
}
class Square extends Enemy{
public Square(int x, int y, double direction, double speed) {
super(x, y, direction, speed);
}
#Override
public void act() {
x += Math.floor(speed * Math.cos(direction));
y += Math.floor(speed * -1 * Math.sin(direction));
}
#Override
public void draw(Graphics2D g2d) {
g2d.setColor(new Color(100, 100, 100));
g2d.fillRect(x, y, 50, 50);
}
}
class Circle extends Enemy{
double mouseX, mouseY;
public Circle(int x, int y, double direction, double speed) {
super(x, y, direction, speed);
}
#Override
public void act() {
mouseX = MouseInfo.getPointerInfo().getLocation().getX();
mouseY = MouseInfo.getPointerInfo().getLocation().getY();
x += speed * Math.floor(Math.abs(y - mouseY) / Math.abs(x - mouseX));
y += speed * -1 * Math.floor(Math.abs(x - mouseX) / Math.abs(y - mouseY));
}
#Override
public void draw(Graphics2D g2d) {
g2d.setColor(new Color(50, 50, 50));
g2d.fillOval(x, y, 10, 10);
}
}
(Run it online here)
Don't know if it was a very specific title, but I have already asked this question but has gone dead.
I am trying to execute paintComponent() so I can draw rectangles, triangles and more with the class being a JComponent.
Here is my code so far:
public class Design extends JComponent {
private static final long serialVersionUID = 1L;
private List<ShapeWrapper> shapesDraw = new ArrayList<ShapeWrapper>();
private List<ShapeWrapper> shapesFill = new ArrayList<ShapeWrapper>();
GraphicsDevice gd = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice();
int screenWidth = gd.getDisplayMode().getWidth();
int screenHeight = gd.getDisplayMode().getHeight();
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
for(ShapeWrapper s : shapesDraw){
g2d.setColor(s.color);
g2d.draw(s.shape);
}
for(ShapeWrapper s : shapesFill){
g2d.setColor(s.color);
g2d.fill(s.shape);
}
}
public void drawRect(int xPos, int yPos, int width, int height) {
shapesDraw.add(new Rectangle(xPos, yPos, width, height));
repaint();
}
public void fillRect(int xPos, int yPos, int width, int height) {
shapesFill.add(new Rectangle(xPos, yPos, width, height));
repaint();
}
public void drawTriangle(int leftX, int topX, int rightX, int leftY, int topY, int rightY) {
shapesDraw.add(new Polygon(
new int[]{leftX, topX, rightX},
new int[]{leftY, topY, rightY},
3));
repaint();
}
public void fillTriangle(int leftX, int topX, int rightX, int leftY, int topY, int rightY) {
shapesFill.add(new Polygon(
new int[]{leftX, topX, rightX},
new int[]{leftY, topY, rightY},
3));
repaint();
}
public Dimension getPreferredSize() {
return new Dimension(getWidth(), getHeight());
}
public int getWidth() {
return screenWidth;
}
public int getHeight() {
return screenHeight;
}
}
class ShapeWrapper {
Color color;
Shape shape;
public ShapeWrapper(Color color , Shape shape){
this.color = color;
this.shape = shape;
}
}
As shown above, everything works perfectly fine, except for being able to choose a colour.
I want to be able to define the rectangles and triangles with their respective positions and lengths but also want to add a colour with it.
But I get an error.
The error says:
The method add(ShapeWrapper) in the type List< ShapeWrapper > is not applicable for the arguments (Rectangle)
And:
The method add(ShapeWrapper) in the type List< ShapeWrapper > is not applicable for the arguments (Polygon)
Please help! I am so stressed trying to figure this out as it is blocking me from doing many things.
The answer is pretty basic...Shape is not a type of ShapeWrapper, therefore it can't be added to a List decalred as List<ShapeWrapper>
What you should be doing instead of
shapesDraw.add(new Rectangle(xPos, yPos, width, height));
is something more like...
shapesDraw.add(new ShapeWrapper(Color.BLACK, new Rectangle(xPos, yPos, width, height)));
The same goes for your ...Triangle methods. You need to wrap the resulting Polygon in a ShapeWrapper before trying to add it to the List
I want to do a bouncing balls application in java. Each ball should take place by mouse clicking and each of them should have random speed, color, radius and starting position. I managed to do everything except the part where mouse listener takes place. Whatever i do in the mousePressed method didn't work. What should i do to make user create a random ball when he presses the mouse?
EDIT: This is the last version of my code. Now the problem is that i can't create more than one ball. When i click on the screen same ball is just keeps speeding.
BouncingBalls Class
public class BouncingBalls extends JPanel implements MouseListener{
private Ball ball;
protected List<Ball> balls = new ArrayList<Ball>(20);
private Container container;
private DrawCanvas canvas;
private int canvasWidth;
private int canvasHeight;
public static final int UPDATE_RATE = 30;
int x = random(480);
int y = random(480);
int speedX = random(30);
int speedY = random(30);
int radius = random(20);
int red = random(255);
int green = random(255);
int blue = random(255);
int count = 0;
public static int random(int maxRange) {
return (int) Math.round((Math.random() * maxRange));
}
public BouncingBalls(int width, int height){
canvasWidth = width;
canvasHeight = height;
ball = new Ball(x, y, speedX, speedY, radius, red, green, blue);
container = new Container();
canvas = new DrawCanvas();
this.setLayout(new BorderLayout());
this.add(canvas, BorderLayout.CENTER);
this.addMouseListener(this);
}
public void start(){
Thread t = new Thread(){
public void run(){
while(true){
update();
repaint();
try {
Thread.sleep(1000 / UPDATE_RATE);
} catch (InterruptedException e) {}
}
}
};
t.start();
}
public void update(){
ball.move(container);
}
class DrawCanvas extends JPanel{
public void paintComponent(Graphics g){
super.paintComponent(g);
container.draw(g);
ball.draw(g);
}
public Dimension getPreferredSize(){
return(new Dimension(canvasWidth, canvasHeight));
}
}
public static void main(String[] args){
javax.swing.SwingUtilities.invokeLater(new Runnable(){
public void run(){
JFrame f = new JFrame("Bouncing Balls");
f.setDefaultCloseOperation(f.EXIT_ON_CLOSE);
f.setContentPane(new BouncingBalls(500, 500));
f.pack();
f.setVisible(true);
}
});
}
#Override
public void mouseClicked(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseEntered(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mouseExited(MouseEvent e) {
// TODO Auto-generated method stub
}
#Override
public void mousePressed(MouseEvent e) {
balls.add(new Ball(x, y, speedX, speedY, radius, red, green, blue));
start();
}
#Override
public void mouseReleased(MouseEvent e) {
// TODO Auto-generated method stub
}
}
Ball Class
import java.awt.Color;
import java.awt.Graphics;
public class Ball{
public static int random(int maxRange) {
return (int) Math.round((Math.random() * maxRange));
}
private BouncingBalls balls;
int x = random(480);
int y = random(480);
int speedX = random(30);
int speedY = random(30);
int radius = random(20);
int red = random(255);
int green = random(255);
int blue = random(255);
int i = 0;
public Ball(int x, int y, int speedX, int speedY, int radius, int red, int green, int blue){
this.x = x;
this.y = y;
this.speedX = speedX;
this.speedY = speedY;
this.radius = radius;
this.red = red;
this.green = green;
this.blue = blue;
}
public void draw(Graphics g){
for(Ball ball : balls){
g.setColor(new Color(red, green, blue));
g.fillOval((int)(x - radius), (int)(y - radius), (int)(2 * radius), (int)(2 * radius));
}
}
public void move(Container container){
x += speedX;
y += speedY;
if(x - radius < 0){
speedX = -speedX;
x = radius;
}
else if(x + radius > 500){
speedX = -speedX;
x = 500 - radius;
}
if(y - radius < 0){
speedY = -speedY;
y = radius;
}
else if(y + radius > 500){
speedY = -speedY;
y = 500 - radius;
}
}
}
Container Class
import java.awt.Color;
import java.awt.Graphics;
public class Container {
private static final int HEIGHT = 500;
private static final int WIDTH = 500;
private static final Color COLOR = Color.WHITE;
public void draw(Graphics g){
g.setColor(COLOR);
g.fillRect(0, 0, WIDTH, HEIGHT);
}
}
ERROR: I get "Can only iterate over an array or an instance of java.lang.Iterable" error in this part of code:
public void draw(Graphics g){
for(Ball ball : balls){
g.setColor(new Color(red, green, blue));
g.fillOval((int)(x - radius), (int)(y - radius), (int)(2 * radius), (int)(2 * radius));
}
}
First, if you want to render more than one ball, you should create another class to contain all the properties needed to draw a ball (maybe even a draw (Graphics g) method to delegate the drawing). Then you would have a list of balls on your BouncingBalls class which should be iterated over and painted on the paintComponent method.
Said that, your mouseClicked handler would just create a new Ball instance and add it to the list.
EDIT:
Example of how the drawing process would be on your DrawCanvas class:
class DrawCanvas {
public void paintComponent(Graphics g){
super.paintComponent(g);
container.draw(g);
for (Ball ball : balls)
//the draw method should only care of the specific ball instance
//you are calling it from
ball.draw(g);
}
...
I think you are having problems separating your problem into classes and making their instances cooperate to do what you want. If you are indeed having doubts about this, I recommend you read some articles/books about the topic to get a better idea of the concepts of a class and an object and how they work; it'll definitely help you do your programming with ease.
You need to add the MouseListener to the component:
public BouncingBalls() {
this.addMouseListener(this); // <-- Add this object as a MouseListener.
this.setPreferredSize(new Dimension(BOX_WIDTH, BOX_HEIGHT));
Try using this code in your main method:
frame.addMouseListener(this);
You need to add the mouse listener to the frame/panel.
(response to this comment by you) Alternatively, if you want to add the listener to the panel, first you must call
setFocusable(true);
requestFocusInWindow();
In your BouncingBalls constructor. Then you can add the mouse listener to the panel with:
addMouseListener(this);
This is because the panel does not initially have focus.
The easiest way to do it, though, is to just add the mouse listener to the frame.
I just want to display my String inside a rectangle filled with black. Thanks for the help!
Here you are:
public class MyPanel extends JPanel{
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawRect(10/*x*/, 10/*y*/, 80/*width*/, 30/*hight*/);
g.drawString("TextToDraw", 25/*x*/, 25/*y*/);
}
}
public class LabeledRectangle extends Rectangle{
public LabeledRectangle(int x, int y, int width, int height, String text) {
super (x, y, width, height);
this.text = text;
}
public void draw(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.draw(new Rectangle2D.Double(x, y, width,height));
Font font = g2.getFont();
FontRenderContext context = g2.getFontRenderContext();
g2.setFont(font);
int textWidth = (int) font.getStringBounds(text, context).getWidth();
LineMetrics ln = font.getLineMetrics(text, context);
int textHeight = (int) (ln.getAscent() + ln.getDescent());
int x1 = x + (width - textWidth)/2;
int y1 = (int)(y + (height + textHeight)/2 - ln.getDescent());
g2.setColor(Color.red);
g2.drawString(text, (int) x1, (int) y1);
}
private String text;
}