I have two rectangles that I need to be taken off screen when they intersect. The rectangles I need to disappear are, bulletObject and e1. They do intersect when I run it but nothing happens. I have tried putting "e1 = new Rectangle (0,0,0,0);" after the "if (bulletObject.intersects(e1)){" but then it tells me that it is never used. All help I appreciated. A chunk of my code is below.
public void draw(Graphics g){
g.setColor(Color.BLUE);
g.fillRect(x, y, 40, 10);
g.fillRect(x+18, y-7, 4, 7);
Rectangle bulletObject = new Rectangle(x+18, y-7, 4, 7);
if (shot){
g.setColor(Color.BLUE);
g.fillRect(bullet.x, bullet.y , bullet.width, bullet.height);
}
//enemies
g.setColor(Color.RED);
Rectangle e1 = new Rectangle(20,75,35,35);
Rectangle e2 = new Rectangle(85,75,35,35);
Rectangle e3 = new Rectangle(150,75,35,35);
Rectangle e4 = new Rectangle(205,75,35,35);
Rectangle e5 = new Rectangle(270,75,35,35);
Rectangle e6 = new Rectangle(335,75,35,35);
Rectangle e7 = new Rectangle(405,75,35,35);
g.setColor(Color.RED);
g.fillRect(e1.x,e1.y,e1.width,e1.height);
g.fillRect(e2.x,e2.y,e2.width,e2.height);
g.fillRect(e3.x,e3.y,e3.width,e3.height);
g.fillRect(e4.x,e4.y,e4.width,e4.height);
g.fillRect(e5.x,e5.y,e5.width,e5.height);
g.fillRect(e6.x,e6.y,e6.width,e6.height);
g.fillRect(e7.x,e7.y,e7.width,e7.height);
g.fillRect(bulletObject.x,bulletObject.y,
bulletObject.width,bulletObject.height);
if (bulletObject.intersects(e1)){
g.clearRect(e1.x, e1.y,e1.width, e1.height );
}
}
Lets start with...
Your paint routines is not the appropriate place to be making decisions about the state of the game, it should be simple responsible for painting the current state.
You need to maintain a List of renderable elements which you can manipluate based on your needs and requirements.
Start by taking a look at Collections
You may also find reading through Performing Custom Lainting and Painting in AWT and Swing useful
The following example demonstrates the basic concept of a series of animated, random, rectangles which will be removed when hit by a fireball, which you trigger by pressing the space bar
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.KeyEvent;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Bullet {
public static void main(String[] args) {
new Bullet();
}
public Bullet() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private List<Rectangle> ships;
private Map<Rectangle, Integer> delats;
private Ellipse2D fireBall;
public TestPane() {
delats = new HashMap<>(25);
ships = new ArrayList<>(25);
Random rnd = new Random();
while (ships.size() < 12) {
boolean intersects = true;
Rectangle rect = null;
while (intersects) {
intersects = false;
int x = (int) (Math.random() * 400);
int y = (int) (Math.random() * 400);
int width = (int) (Math.random() * 50) + 25;
int height = (int) (Math.random() * 50) + 25;
if (x + width >= 400) {
x = 400 - width;
} else if (y + height >= 400) {
y = 400 - height;
}
rect = new Rectangle(x, y, width, height);
for (Rectangle other : ships) {
if (other.intersects(rect)) {
intersects = true;
break;
}
}
}
ships.add(rect);
delats.put(rect, (rnd.nextBoolean() ? 1 : -1));
}
Timer timer;
timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (fireBall != null) {
Rectangle bounds = fireBall.getBounds();
bounds.x += 5;
if (bounds.x >= getWidth()) {
fireBall = null;
} else {
fireBall.setFrame(bounds);
}
}
Iterator<Rectangle> it = ships.iterator();
while (it.hasNext()) {
Rectangle rct = it.next();
int delta = delats.get(rct);
rct.y += delta;
if (rct.y + rct.height >= getHeight()) {
rct.y = getHeight() - rct.height;
delta *= -1;
} else if (rct.y <= 0) {
rct.y = 0;
delta *= -1;
}
delats.put(rct, delta);
if (fireBall != null) {
if (fireBall.intersects(rct)) {
it.remove();
delats.remove(rct);
}
}
}
repaint();
}
});
timer.start();
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), "fire");
ActionMap am = getActionMap();
am.put("fire", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
fireBall = new Ellipse2D.Float(0, (getHeight() / 2) - 5, 10, 10);
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
if (fireBall != null) {
g2d.setColor(Color.RED);
g2d.fill(fireBall);
}
g2d.setColor(Color.BLACK);
for (Rectangle rct : ships) {
g2d.draw(rct);
}
g2d.dispose();
}
}
}
It sounds like you are new to programming since you said you don't understand what a list is. I would suggest you check out the java tutorials available online (for instance for understanding lists and other collections: http://docs.oracle.com/javase/tutorial/collections/).
As far as your question, I agree with MadProgrammer, you should have a list of objects which your draw function draws and when a rectangle is "hit" then you should remove it from this list.
For instance to create your list use:
List<Shape> shapeList = new ArrayList<Shape>();
Rectangle e1 = new Rectangle(20,75,35,35);
shapeList.add(e1);
Rectangle e2 = new Rectangle(85,75,35,35);
shapeList.add(e2);
//repeat for each rectangle
This shouldn't be in your draw method as this method will be called many times in your program I assume and you don't want to have to recreate this list and each rectangle every time you do this.
I suggest you look at the documentation for List and ArrayList and learn how to use them well since you will use them almost every time you write a program in Java.
Good Luck
Store your Rectangles in some kind of data structure - if you know the maximum possible numer of Rectangles you may use an array. If not - a list would be a better choice as its size is not fixed (you just add and remove the elements to/from the list and do not care much about its size - you can read more about Lists here: http://docs.oracle.com/javase/tutorial/collections/implementations/list.html , online search should also be fruitful.)
Once all your Rectangles are in an array or a list (or any other collection you choose) you can paint all the rectangles that are in the collection at the moment. If two or more of them collide - remove them from the collection and repaint (so again paint all the rectangles which are in the collection now).
If all of this appear to be unfamiliar for you (arrays, collections...) I highly recommend you check out some Java tutorials (or general programming tutorials) - for example the official Java Turorial by Oracle: http://docs.oracle.com/javase/tutorial/tutorialLearningPaths.html
Related
Let's say I have a grid with images in Java.
I now draw the images in the Graphics2D component g as follows:
g.drawImage(image, 50 * cellWidth, 50 * cellHeight, cellWidth, cellHeight, Color.WHITE, null)
I'm now interested in rotating the image (while staying in the same grid row and column) 90 degrees in a given direction.
Could someone help me accomplish this?
First, you need a Graphics2D context. In most cases when supplied with a Graphics it's actually an instance of Graphics2D so you can simply cast it.
Having said that though, when perform transformations, it's always useful to create a new context (this copies the state only)...
Graphics2D g2d = (Graphics2D) g.create();
Next, you want to translate the origin point. This makes it a lot easier to do things like rotation.....
g2d.translate(50 * cellWidth, 50 * cellHeight);
Then you can rotate the context around the centre point of the cell (remember, 0x0 is now our cell offset)...
g2d.rotate(Math.toRadians(90), cellWidth / 2, cellWidth / 2);
And then we can simply draw the image...
g2d.drawImage(image, 0, 0, cellWidth, cellHeight, Color.WHITE, null);
And don't forget to dispose of the copy when you're done
g2d.dispose();
You might also want to take a look at The 2D Graphics trail, as you could use a AffineTransformation instead, but it'd be accomplishing the same thing, more or less
Is there a way to actually see the rotating happening (so see the rotation "live")?
Animation is a complex subject, add in the fact that Swing is single threaded and not thread safe and you need to think carefully about it.
Have a look at Concurrency in Swing and How to Use Swing Timers for more details.
Simple animation
The following example makes use of simple Swing Timer to rotate a image when it's clicked. The example makes use of time based approach (ie the animation runs over a fixed period of time). This produces a better result then a linear/delta approach.
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.time.Duration;
import java.time.Instant;
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 Simple {
public static void main(String[] args) throws IOException {
new Simple();
}
public Simple() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException ex) {
Logger.getLogger(Advanced.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
}
public class TestPane extends JPanel {
private List<BufferedImage> images;
private BufferedImage selectedImage;
public TestPane() throws IOException {
images = new ArrayList<>(9);
for (int index = 0; index < 9; index++) {
BufferedImage img = ImageIO.read(getClass().getResource("/images/p" + (index + 1) + ".png"));
images.add(img);
}
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
if (selectedImage != null) {
return;
}
int col = (e.getX() - 32) / 210;
int row = (e.getY() - 32) / 210;
int index = (row * 3) + col;
selectedImage = images.get(index);
startTimer();
}
});
}
private Timer timer;
private Instant startedAt;
private Duration duration = Duration.ofSeconds(1);
private double maxAngle = 1440;
private double currentAngle = 0;
protected void startTimer() {
if (timer != null) {
return;
}
timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (startedAt == null) {
startedAt = Instant.now();
}
Duration runtime = Duration.between(startedAt, Instant.now());
double progress = runtime.toMillis() / (double)duration.toMillis();
if (progress >= 1.0) {
progress = 1.0;
selectedImage = null;
startedAt = null;
stopTimer();
}
currentAngle = maxAngle * progress;
repaint();;
}
});
timer.start();
}
protected void stopTimer() {
if (timer == null) {
return;
}
timer.stop();
timer = null;
}
#Override
public Dimension getPreferredSize() {
return new Dimension((210 * 3) + 64, (210 * 3) + 64);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.translate(32, 32);
int row = 0;
int col = 0;
for (BufferedImage img : images) {
int x = col * 210;
int y = row * 210;
Graphics2D gc = (Graphics2D) g2d.create();
gc.translate(x, y);
if (selectedImage == img) {
gc.rotate(Math.toRadians(currentAngle), 210 / 2, 210 / 2);
}
gc.drawImage(img, 0, 0, this);
gc.dispose();
col++;
if (col >= 3) {
col = 0;
row++;
}
}
g2d.dispose();
}
}
}
nb: My images are 210x210 in size and I'm been naughty with not using the actual sizes of the images, and using fixed values instead
Advanced animation
While the above example "works", it becomes much more complicated the more you add it. For example, if you want to have multiple images rotate. Towards that end, you will need to keep track of some kind of model for each image which contains the required information to calculate the current rotation value.
Another issue is, what happens if you want to compound the animation? That is, scale and rotate the animation at the same time.
Towards this end, I'd lean towards using concepts like "time lines" and "key frames"
The following example is based on my personal library Super Simple Swing Animation Framework. This is bit more of a playground for me then a fully fledged animation framework, but it embodies many of the core concepts which help make animating in Swing simpler and help produce a much nicer result
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import org.kaizen.animation.Animatable;
import org.kaizen.animation.AnimatableAdapter;
import org.kaizen.animation.AnimatableDuration;
import org.kaizen.animation.DefaultAnimatableDuration;
import org.kaizen.animation.curves.Curves;
import org.kaizen.animation.timeline.BlendingTimeLine;
import org.kaizen.animation.timeline.DoubleBlender;
public class Advanced {
public static void main(String[] args) throws IOException {
new Advanced();
}
public Advanced() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException ex) {
Logger.getLogger(Advanced.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
}
public class TestPane extends JPanel {
private List<BufferedImage> images;
private Map<BufferedImage, Double> imageZoom = new HashMap<>();
private Map<BufferedImage, Double> imageRotate = new HashMap<>();
private BlendingTimeLine<Double> zoomTimeLine;
private BlendingTimeLine<Double> rotateTimeLine;
public TestPane() throws IOException {
zoomTimeLine = new BlendingTimeLine<>(new DoubleBlender());
zoomTimeLine.addKeyFrame(0, 1.0);
zoomTimeLine.addKeyFrame(0.25, 1.5);
zoomTimeLine.addKeyFrame(0.75, 1.5);
zoomTimeLine.addKeyFrame(1.0, 1.0);
rotateTimeLine = new BlendingTimeLine<>(new DoubleBlender());
rotateTimeLine.addKeyFrame(0d, 0d);
rotateTimeLine.addKeyFrame(0.1, 0d);
// rotateTimeLine.addKeyFrame(0.85, 360.0 * 4d);
rotateTimeLine.addKeyFrame(1.0, 360.0 * 4d);
images = new ArrayList<>(9);
for (int index = 0; index < 9; index++) {
BufferedImage img = ImageIO.read(getClass().getResource("/images/p" + (index + 1) + ".png"));
images.add(img);
}
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
int col = (e.getX() - 32) / 210;
int row = (e.getY() - 32) / 210;
int index = (row * 3) + col;
BufferedImage selectedImage = images.get(index);
if (imageZoom.containsKey(selectedImage)) {
return;
}
animate(selectedImage);
}
});
}
protected void animate(BufferedImage img) {
Animatable animatable = new DefaultAnimatableDuration(Duration.ofSeconds(1), Curves.CUBIC_IN_OUT.getCurve(), new AnimatableAdapter<Double>() {
#Override
public void animationTimeChanged(AnimatableDuration animatable) {
double progress = animatable.getProgress();
Double desiredZoom = zoomTimeLine.getValueAt(progress);
imageZoom.put(img, desiredZoom);
double desiredAngle = rotateTimeLine.getValueAt(progress);
imageRotate.put(img, desiredAngle);
repaint();
}
#Override
public void animationStopped(Animatable animator) {
imageZoom.remove(img);
imageRotate.remove(img);
repaint();
}
});
animatable.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension((210 * 3) + 64, (210 * 3) + 64);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.translate(32, 32);
int row = 0;
int col = 0;
for (BufferedImage img : images) {
if (!(imageZoom.containsKey(img) || imageRotate.containsKey(img))) {
int x = col * 210;
int y = row * 210;
Graphics2D gc = (Graphics2D) g2d.create();
gc.translate(x, y);
gc.drawImage(img, 0, 0, this);
gc.dispose();
}
col++;
if (col >= 3) {
col = 0;
row++;
}
}
row = 0;
col = 0;
for (BufferedImage img : images) {
if (imageZoom.containsKey(img) || imageRotate.containsKey(img)) {
int x = col * 210;
int y = row * 210;
Graphics2D gc = (Graphics2D) g2d.create();
gc.translate(x, y);
double width = img.getWidth();
double height = img.getHeight();
double zoom = 1;
if (imageZoom.containsKey(img)) {
zoom = imageZoom.get(img);
width = (img.getWidth() * zoom);
height = (img.getHeight() * zoom);
double xPos = (width - img.getWidth()) / 2d;
double yPos = (height - img.getHeight()) / 2d;
gc.translate(-xPos, -yPos);
}
if (imageRotate.containsKey(img)) {
double angle = imageRotate.get(img);
gc.rotate(Math.toRadians(angle), width / 2, height / 2);
}
gc.scale(zoom, zoom);
gc.drawImage(img, 0, 0, this);
gc.dispose();
}
col++;
if (col >= 3) {
col = 0;
row++;
}
}
g2d.dispose();
}
}
}
nb: The paint workflow is a little more complicated (and could be optimised more) as it focuses on painting the images which are been animated onto of the others, which results in a much nicer result
I have a timer for my set of icons which animates these icons. Basically - I have a world map as a background and car,airplane,etc. icons "floating" on this map. I'm using a swing timer to do this, but it makes all the icons to appear and start their motion as soon as the JPanel is rendered. I want them to appear/start their motion at random time, I thought about implementing a 2nd timer and every 10 second create a random number - either 1 or 2 - (e.g and if the number is equal to 1 it starts the animation for this particular icon).
Timer timer;
....
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.drawImage(world_map,0,0, getWidth(), getHeight(), null);
for (Transportation item : transportations) {
item.drawTransportation(g);
g2d.drawLine(item.start_x+25,item.start_y+25,item.finish_x+25,item.finish_y+25);
}
}
#Override
public void actionPerformed(ActionEvent e) {
for (Transportation item : transportations) {
if (item.x > item.finish_x) {
item.x -= 1;
}
else if (item.x < item.finish_x) {
item.x += 1;
}
if (item.y > item.finish_y) {
item.y -= 1;
}
else if (item.y < item.finish_y) {
item.y += 1;
}
}
repaint();
}
public void startCoords() {
for (Transportation item : transportations) {
item.setMap_coords(map_coords);
item.setCountries(countries);
}
timer = new Timer(10,this);
timer.start();
}
"Generally" speaking, multiple Swing Timers don't scale well. And if you're trying to develop a game of some sort, keeping all the "updates" within a single "game loop" adds a lot of benefit when it comes to keeping things under control.
So, this is a really simply concept. When the Timer ticks, it checks for the amount of time which has passed and when some kind of delta is reached, it generates a bunch of "random things".
I also put a little counter in so you can see that something is actually still happening along the way.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private List<Point> points = new ArrayList<>(25);
private Timer timer;
private Instant clock;
private Random rnd = new Random();
public TestPane() {
setBackground(Color.BLACK);
setForeground(Color.WHITE);
timer = new Timer(5, new ActionListener() {
private Instant anchorTime;
#Override
public void actionPerformed(ActionEvent arg0) {
if (anchorTime == null) {
anchorTime = Instant.now();
}
// You could just do a mod 10, but I'm feeling lazy
if (Duration.between(anchorTime, Instant.now()).getSeconds() >= 10) {
anchorTime = Instant.now();
generateRandomThing();
}
repaint();
}
});
timer.setInitialDelay(0);
clock = Instant.now();
}
protected void generateRandomThing() {
for (int index = 0; index < 10; index++) {
int x = 10 + (int) (rnd.nextDouble() * (getWidth() - 20));
int y = 10 + (int) (rnd.nextDouble() * (getHeight() - 20));
Point p = new Point(x, y);
points.add(p);
}
}
#Override
public void addNotify() {
super.addNotify();
clock = Instant.now();
timer.start();
}
#Override
public void removeNotify() {
super.removeNotify(); //To change body of generated methods, choose Tools | Templates.
timer.stop();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
Duration runTime = Duration.between(clock, Instant.now());
g2d.setColor(Color.WHITE);
FontMetrics fm = g2d.getFontMetrics();
String text = Long.toString(runTime.getSeconds());
int x = 10;
int y = 10 + fm.getAscent();
g2d.drawString(text, x, y);
g2d.setColor(Color.RED);
for (Point p : points) {
g2d.fillOval(p.x - 5, p.y - 5, 10, 10);
}
g2d.dispose();
}
}
}
This is, by no means, the only approach you can take, it's just one of a question of scalability and control.
I would like to rotate a rectangle when e.g. y position achieve specified position. I would like to behave a rectangle as a car on a junction - just turn e.g. right. I prefer just rotate and continue.
A draft code looks like that:
Graphics2D g2d = (Graphics2D) g.create();
g2d.setPaint(new Color(150, 150, 0));
//1. when rectangle achieve 500 on y dimension just rotate left/right
if(y==500) {
_rotate = true;
g2d.rotate(Math.toRadians(90.));
}
if(_rotate) { //if rotate, continue way on x dimension
++x ;
g2d.fillRect(x, y, 20, 40);
} else { //else go to the north
--y;
g2d.fillRect(x, y, 20, 40);
}
There is a lot of information which is missing from your question.
In order to be able to rotate a shape, you need to know a few things, you need to know it's current position and it's next target position, then you can simply calculate the angle between these two points.
The question then becomes, how do you calculate these positions. There are plenty of ways you might achieve this, the following is a simple path following process.
First, we generate a path which we need to follow, we use the Shape API to calculate the points along the path. We use a simple time based animation (rather the looping through the points, we calculate the progress along the path by calculating amount of time the animation has been playing divided by the amount of time we want it to take) and picking the point which best matches our current progress.
We use a AffineTransform to rotate the player shape and the translate the resulting Shape to the required position. Ease
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import java.awt.geom.PathIterator;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class PathFollow {
public static void main(String[] args) {
new PathFollow();
}
public PathFollow() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private Shape pathShape;
private List<Point2D> points;
private Shape car;
private double angle;
private Point2D pos;
private int index;
protected static final double PLAY_TIME = 5000; // 5 seconds...
private Long startTime;
public TestPane() {
Path2D path = new Path2D.Double();
path.moveTo(0, 200);
path.curveTo(100, 200, 0, 100, 100, 100);
path.curveTo(200, 100, 0, 0, 200, 0);
pathShape = path;
car = new Rectangle(0, 0, 10, 10);
points = new ArrayList<>(25);
PathIterator pi = pathShape.getPathIterator(null, 0.01);
while (!pi.isDone()) {
double[] coords = new double[6];
switch (pi.currentSegment(coords)) {
case PathIterator.SEG_MOVETO:
case PathIterator.SEG_LINETO:
points.add(new Point2D.Double(coords[0], coords[1]));
break;
}
pi.next();
}
// System.out.println(points.size());
// pos = points.get(0);
// index = 1;
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (startTime == null) {
startTime = System.currentTimeMillis();
}
long playTime = System.currentTimeMillis() - startTime;
double progress = playTime / PLAY_TIME;
if (progress >= 1.0) {
progress = 1d;
((Timer) e.getSource()).stop();
}
int index = Math.min(Math.max(0, (int) (points.size() * progress)), points.size() - 1);
pos = points.get(index);
if (index < points.size() - 1) {
angle = angleTo(pos, points.get(index + 1));
}
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.draw(pathShape);
AffineTransform at = new AffineTransform();
if (pos != null) {
Rectangle bounds = car.getBounds();
at.rotate(angle, (bounds.width / 2), (bounds.width / 2));
Path2D player = new Path2D.Double(car, at);
g2d.translate(pos.getX() - (bounds.width / 2), pos.getY() - (bounds.height / 2));
g2d.draw(player);
}
g2d.dispose();
}
// In radians...
protected double angleTo(Point2D from, Point2D to) {
double angle = Math.atan2(to.getY() - from.getY(), to.getX() - from.getX());
return angle;
}
}
}
Is there an easier way to code my program such that I can draw my tile-based map onto a Panel (of some sort), such that the map wont redraw each time I resize the window (with resizable off)? I realize that that is great for debugging and testing my mapDrawing function, but, I also don't think I'm doing it ideally, or even in a smart way at all.
My code is as follows.. if you need my subclasses for some reason, I can edit those in too.
import java.awt.*;
import javax.swing.*;
public class AhnkorMyst extends JPanel { // main game class
static final int screenWidth = 760;
static final int screenHeight = 760;
public void paintComponent(Graphics g) {
super.paintComponent(g); // paint background
setBackground(Color.BLACK);
Graphics2D g2d = (Graphics2D) g;
Map newMap = new Map(g2d, screenWidth, screenHeight);
newMap.generateBaseMap();
newMap.populateSurroundings();
newMap.quadSmoothingIteration ();
int i, j;
for (j = 0; j < (newMap.mapHeight / 20); j++) {
for (i = 0; i < (newMap.mapWidth / 20); i++) {
newMap.mainMap[i][j].paint();
}
}
}
public static void main (String[] args) {
AhnkorMyst game = new AhnkorMyst();
JFrame frame = new JFrame("Ahnkor Myst");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(game);
frame.setSize(screenWidth + 10, screenHeight + 30);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
frame.setResizable(false);
}
}
edit** my Map is randomly generated with the generateBaseMap () function.
This is "very" basic example of the concept. Basically, this re-builds the BufferedImage which represents the basic "view" of the map every time the JPanel is invalidated.
You should note, that I simple randomise the map each time it is built, presumably, you will be using some kind of virtual structure which defines the map itself and would use this to build the map instead...
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Random;
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.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class TestTiles {
public static void main(String[] args) {
new TestTiles();
}
public TestTiles() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TileMap());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TileMap extends JPanel {
private int tileColumns = 8;
private int tileRows = 8;
private BufferedImage tileSheet;
private BufferedImage tileMap;
public TileMap() {
try {
tileSheet = ImageIO.read(getClass().getResource("/TileSet.png"));
} catch (IOException ex) {
ex.printStackTrace();
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
public void invalidate() {
tileMap = null;
super.invalidate();
}
protected void buildMap() {
tileMap = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics2D g2d = tileMap.createGraphics();
int tileWidth = tileSheet.getWidth() / tileColumns;
int tileHeight = tileSheet.getHeight() / tileRows;
Random random = new Random();
for (int x = 0; x < getWidth(); x += tileWidth) {
for (int y = 0; y < getHeight(); y += tileHeight) {
int xCell = random.nextInt(tileColumns - 1) * tileWidth;
int yCell = random.nextInt(tileRows - 1) * tileHeight;
BufferedImage tile = tileSheet.getSubimage(xCell, yCell, tileWidth, tileHeight);
g2d.drawImage(tile, x, y, this);
}
}
g2d.dispose();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (tileSheet != null) {
Graphics2D g2d = (Graphics2D) g.create();
if (tileMap == null) {
buildMap();
}
g2d.drawImage(tileMap, 0, 0, this);
g2d.dispose();
}
}
}
}
You could take this concept further and pre-generate the entire world into a single BufferedImage and use getSubImage to grab a smaller portion which what you want to display. This starts to form the basic concept of scrolling, as you could maintain a virtual position in the world and calculate what portion of the map would need to be shown to represent it...
Avoid lengthy calculations and instantiations in your implementation of paintComponent(). You can get an idea of the available rendering budget on your target platform using the approach shown in this AnimationTest. Instead, pre-compute as much as possible. In this tile example, the ground map is entirely static, and the rendering is handled by paintIcon(). A related example is examined here.
I've recently been working on putting together a program that can track one's success during a basketball game. I have a court diagram set up, and my idea is that after clicking a "make" or "miss" button, you can click a spot on the court and it will chart your makes and misses while keeping tallies of both off to the side. I was thinking the easiest way to do this was to fill the court with a huge grid of buttons (20x20), and when you click one it would change the text to an "x" or an "o" depending on if it was a made shot. I have no problems for creating listeners for the various buttons, but the buttons show up over top of the graphics of the court lines. Making the buttons transparent (which I tried) makes it look a little better, but my ideal solution would be to make the buttons in the grid invisible, yet clickable and able to change its text after being clicked.
I am yet to find such a method or other way to make this happen, so if anyone has any experience or input in creating something that could make it function in this way would help. I guess my ultimate question would be is there a way to make buttons invisible, yet clickable? If there is no simple way to do this (which I fear), are there any other ideas that you have that could make this program function effectively that might not involve buttons?
Thank you very much and any ideas would help me out a lot.
I also think I might note that I have a separate driver from the code, even though it probably does not make a difference.
You could do something like this
For the two buttons, just change the boolean make. You'll see why later
boolean make = false;
makeButton.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent){
make = true;
}
});
missButton.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent){
make = false;
}
});
Then you have your paintComponent
protected void paintComponent(Graphics g){
super.paintComponent(g);
if (make){
drawOval(whataver are your point requirements)
} else {
// draw an x at whatever points
}
}
You can see from above that I made use of the made variable
Now to get the location you can do something like this
Point p;
courtPanel.addMouseListener(new MouseAdapter(){
public void mouseClicked(MouseEvent e){
p = e.getLocationOnScreen();
repaint();
}
});
You paint component will paint based off those point coordinates.
SO basically, what all the thing I listed above does is:
1) When the makeButton is pressed, it changes the setting to made so when the panel is painted, it gets painted with a circle and an X when the missButtton is pressed.
2) I add a mouseListener to thecourtPanelbecuase everytime the panel is clicked somewhere, that's the point on the floor where is it either painted anxor acircle`
If you want multiple X and circle painted you do something like this
private boolean make = false;
private HashMap<Boolean, Point> points = new HaspMap<>();
makeButton.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent){
make = true;
}
});
missButton.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent){
make = false;
}
});
courtPanel.addMouseListener(new MouseAdapter(){
public void mouseClicked(MouseEvent e){
if (make) {
points.put(true, e.getLocationOnScreen());
repaint();
} else {
points.put(false, e.getLocationOnScreen());
repaint();
}
});
protected void paintComponent(Graphics g){
super.paintComponent(g);
for (Map.Entry<Boolean, Point> entry : points.entrySet()) {
Boolean key = entry.getKey();
Point point = entry.getValue();
if (key) {
g.grawOval(point.getX(), point.getY(), 10, 20);
} esle {
g.drawLine(.., .., .., ..); //draws one half of `X`
g.drawLine(.., .., .., ..); //draws other half
}
}
}
The problem with transparent components is, basically, it's next to near impossible to actually click them ;)
Another solution might be to use a MouseListener on the component rendering the map and simple make the translation from click point to "virtual grid", which you can then renderer over the top, 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.Point;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class BaseBallMap {
public static void main(String[] args) {
new BaseBallMap();
}
public BaseBallMap() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public static class TestPane extends JPanel {
public static final int GRID_COUNT = 20;
private BufferedImage map;
private List<Point> cells;
public TestPane() {
cells = new ArrayList<>(400);
try {
map = ImageIO.read(new File("Map.png"));
} catch (IOException ex) {
ex.printStackTrace();
}
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
Point p = e.getPoint();
if (getMapBounds().contains(p)) {
p.x -= getXOffset();
p.y -= getYOffset();
int col = p.x / getColumnWidth();
int row = p.y / getRowHeight();
System.out.println(col + "x" + row);
Point cell = new Point(col, row);
if (cells.contains(cell)) {
cells.remove(cell);
} else {
cells.add(cell);
}
repaint();
}
}
});
}
#Override
public Dimension getPreferredSize() {
return map == null ? new Dimension(200, 200) : new Dimension(map.getWidth(), map.getHeight());
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (map != null) {
Graphics2D g2d = (Graphics2D) g.create();
int xOffset = getXOffset();
int yOffset = getYOffset();
int x = xOffset;
int y = yOffset;
g2d.drawImage(map, x, y, this);
int colWidth = getColumnWidth();
int rowHeight = map.getHeight() / GRID_COUNT;
g2d.setColor(new Color(255, 0, 0, 128));
for (Point p : cells) {
x = xOffset + (p.x * colWidth);
y = yOffset + (p.y * rowHeight);
g2d.fillRect(x, y, colWidth, rowHeight);
}
g2d.setColor(new Color(128, 128, 128, 64));
for (int col = 0; col < GRID_COUNT; col++) {
x = xOffset + (col * colWidth);
g2d.drawLine(x, yOffset, x, yOffset + map.getHeight());
}
for (int row = 0; row < GRID_COUNT; row++) {
y = yOffset + (row * rowHeight);
g2d.drawLine(xOffset, y, xOffset + map.getWidth(), y);
}
g2d.drawRect(xOffset, yOffset, map.getWidth(), map.getHeight());
g2d.dispose();
}
}
protected int getColumnWidth() {
return map == null ? 0 : map.getWidth() / GRID_COUNT;
}
protected int getRowHeight() {
return map == null ? 0 : map.getHeight() / GRID_COUNT;
}
protected int getXOffset() {
return map == null ? 0 : (getWidth() - map.getWidth()) / 2;
}
protected int getYOffset() {
return map == null ? 0 : (getHeight() - map.getHeight()) / 2;
}
protected Rectangle getMapBounds() {
return map == null ? new Rectangle(0, 0, 0, 0) : new Rectangle(getXOffset(), getYOffset(), map.getWidth(), map.getHeight());
}
}
}