I'm making a 'game' of sorts where the player has to click on an image bouncing around the screen. The catch is that the screen is in darkness and the mouse cursor is a 'flashlight' which 'light up' a small circle around it.
I have a JFrame
in one class consisting of:
public class GameFrame {
public static void main(String[] args) throws IOException {
Dimension d = Toolkit.getDefaultToolkit().getScreenSize();
JFrame jf = new JFrame("Flashlight Game");
jf.setVisible(true);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.setSize(d);
jf.setLocationRelativeTo(null);
GamePanel gp = new GamePanel();
jf.add(gp);
}
}
I have another class which extends
JPanel
. Here are its fields relevant to my problems:
private Point mouse; //location set by a MouseMotionListener
private BufferedImage myImage;
private int imageX;
private int imageY;
private int imageSpeedX;
private int imageSpeedY;
My first problem lies in the flashlight. In my paint
method, I set the graphics background color to the panel background color and use the clearRect
method to clear an area around the mouse cursor.
public void paint(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
super.paint(g2);
checkBounce();
move();
g2.drawImage(myImage, imageX, imageY, null);
g2.setColor(Color.BLACK);
g2.fillRect(0, 0, this.getWidth(), this.getHeight());
g2.setBackground(Color.WHITE);
g2.clearRect((int) mouse.getX() - 25, (int) mouse.getY() - 25, 50, 50);
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
repaint();
}
There are actually two problems here.
1.) How can I create a clearOval
effect since a flashlight doesn't shine in a rectangle, and
2.) How can I get the bouncing image to show up through the flashlight beam? I know that calling g2.setBackground(Color.WHITE)
will use the set color as the 'background' for the cleared area, but I need a way to clear all graphics except for the backmost JFrame
or JPanel
background color.
My last problem is sorta weird, but occasionally when I change the value of an int
, the window will appear as blank and needs resizing before any of the code will execute.
The basic idea is to create a Rectangle
big enough to cover the component, create a Ellipse2D
to act as the "hole" or "spot light" and subtract the Ellipse2D
from Rectangle
so as to create a hole in it, then paint it.
- You should avoid overridding
paint
where possible and instead, use paintComponent
, paint
tends to be to high in the paint chain and comes with a number of complications which are best avoided
- You should avoid changing the state the state of your component or model within any
paintXxx
method. Paint can be called for any number of reasons, many of which you won't instantiate. This could be putting your models state into a inconsistent state. Instead, you should using something like a javax.swing.Timer
to regulate when the model is updated and simply call repaint
- Don't ever call
Thread.sleep
, Threasd.wait
or perform any kind of long running loop or I/O operation within the context of the Event Dispatching Thread. Swing is a single threaded environment. That is, all the updates to the UI and event processing are done from within a single thread. If you do anything that blocks the EDT, it will be unable to process these events and update the UI until you stop blocking...
- Don't call
repaint
or any method that might invoke a repaint from within in paintXxx
method. This will place your program into a spiral of death that will see it consume your CPU...
For more details, take a look at...
- Performing Custom Painting
- Concurrency in Swing
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.RenderingHints;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.Area;
import java.awt.geom.Ellipse2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
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 Spotlight {
public static void main(String[] args) {
new Spotlight();
}
public Spotlight() {
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 class TestPane extends JPanel {
public static final int RADIUS = 80;
private BufferedImage img;
private Point mousePoint;
public TestPane() {
try {
img = ImageIO.read(new File("C:\\hold\\thumbnails\\Rampage_Small.png"));
} catch (IOException ex) {
Logger.getLogger(Spotlight.class.getName()).log(Level.SEVERE, null, ex);
}
addMouseMotionListener(new MouseAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
mousePoint = e.getPoint();
repaint();
}
});
}
@Override
public Dimension getPreferredSize() {
return img == null ? new Dimension(200, 200) : new Dimension(img.getWidth(), img.getHeight());
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (img != null) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.setRenderingHint(RenderingHints.KEY_ALPHA_INTERPOLATION, RenderingHints.VALUE_ALPHA_INTERPOLATION_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHint(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_DITHERING, RenderingHints.VALUE_DITHER_ENABLE);
g2d.setRenderingHint(RenderingHints.KEY_FRACTIONALMETRICS, RenderingHints.VALUE_FRACTIONALMETRICS_ON);
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
g2d.setRenderingHint(RenderingHints.KEY_STROKE_CONTROL, RenderingHints.VALUE_STROKE_PURE);
int x = (getWidth() - img.getWidth()) / 2;
int y = (getHeight() - img.getHeight()) / 2;
g2d.drawImage(img, x, y, this);
x = mousePoint == null ? getWidth() / 2 : mousePoint.x;
y = mousePoint == null ? getHeight() / 2 : mousePoint.y;
Rectangle rect = new Rectangle(0, 0, getWidth(), getHeight());
Ellipse2D spot = new Ellipse2D.Float(
(float) x - (RADIUS / 2f),
(float) y - (RADIUS / 2f),
(float) RADIUS,
(float) RADIUS);
Area area = new Area(rect);
area.subtract(new Area(spot));
g2d.setColor(Color.BLACK);
g2d.fill(area);
g2d.dispose();
}
}
}
}