JProgressbar: how to change colour based on progress? - java

Is it possible to be able to change the bar colour depending on the value of the progress? I tried the following but it doesn't work:
percentUsed = (int)(((float) used / (float) max) * BAR_PERCENTAGE);
if (percentUsed >= ORANGE_THRESHOLD && percentUsed < RED_THRESHOLD) {
if (!m_orangeIndicator) {
LOG.warn(String.format("Memory usage exceeds %d percent.", ORANGE_THRESHOLD));
m_orangeIndicator = true;
}
colour = Color.ORANGE;
m_redIndicator = false;
}
else if (percentUsed >= RED_THRESHOLD) {
if (!m_redIndicator) {
LOG.warn(String.format("Memory usage exceeds %d percent.", RED_THRESHOLD));
m_orangeIndicator = true;
m_redIndicator = true;
}
colour = Color.RED;
}
else {
m_orangeIndicator = false;
m_redIndicator = false;
colour = Color.GREEN;
}
m_memUsageBar.setForeground(colour);
m_memUsageBar.setValue(percentUsed);
m_memUsageBar.updateUI();
I am guessing it is not a trivial thing to do because JProgressbar is not meant to be used that way... But is it possible or are there alternatives?

This sample was inspired from:
http://harmoniccode.blogspot.jp/2011/05/varying-gradients.html
http://java-swing-tips.blogspot.jp/2011/06/gradient-translucent-track-jslider.html
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.*;
import java.beans.*;
import javax.swing.*;
import javax.swing.plaf.basic.*;
import javax.swing.event.*;
public class GradientPalletProgressBarDemo {
public JComponent makeUI() {
final JProgressBar progressBar = new JProgressBar();
progressBar.setOpaque(false);
progressBar.setUI(new GradientPalletProgressBarUI());
JPanel p = new JPanel();
p.add(progressBar);
p.add(new JButton(new AbstractAction("Start") {
#Override public void actionPerformed(ActionEvent e) {
SwingWorker<Void,Void> worker = new SwingWorker<Void,Void>() {
#Override public Void doInBackground() {
int current = 0, lengthOfTask = 100;
while(current<=lengthOfTask && !isCancelled()) {
try { // dummy task
Thread.sleep(50);
} catch(InterruptedException ie) {
return null;
}
setProgress(100 * current / lengthOfTask);
current++;
}
return null;
}
};
worker.addPropertyChangeListener(new ProgressListener(progressBar));
worker.execute();
}
}));
return p;
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override public void run() {
createAndShowGUI();
}
});
}
public static void createAndShowGUI() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
frame.getContentPane().add(new GradientPalletProgressBarDemo().makeUI());
frame.setSize(320, 240);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
class ProgressListener implements PropertyChangeListener {
private final JProgressBar progressBar;
ProgressListener(JProgressBar progressBar) {
this.progressBar = progressBar;
this.progressBar.setValue(0);
}
#Override public void propertyChange(PropertyChangeEvent evt) {
String strPropertyName = evt.getPropertyName();
if("progress".equals(strPropertyName)) {
progressBar.setIndeterminate(false);
int progress = (Integer)evt.getNewValue();
progressBar.setValue(progress);
}
}
}
class GradientPalletProgressBarUI extends BasicProgressBarUI {
private final int[] pallet;
public GradientPalletProgressBarUI() {
super();
this.pallet = makeGradientPallet();
}
private static int[] makeGradientPallet() {
BufferedImage image = new BufferedImage(100, 1, BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = image.createGraphics();
Point2D start = new Point2D.Float(0f, 0f);
Point2D end = new Point2D.Float(99f, 0f);
float[] dist = {0.0f, 0.5f, 1.0f};
Color[] colors = { Color.RED, Color.YELLOW, Color.GREEN };
g2.setPaint(new LinearGradientPaint(start, end, dist, colors));
g2.fillRect(0, 0, 100, 1);
g2.dispose();
int width = image.getWidth(null);
int[] pallet = new int[width];
PixelGrabber pg = new PixelGrabber(image, 0, 0, width, 1, pallet, 0, width);
try {
pg.grabPixels();
} catch(Exception e) {
e.printStackTrace();
}
return pallet;
}
private static Color getColorFromPallet(int[] pallet, float x) {
if(x < 0.0 || x > 1.0) {
throw new IllegalArgumentException("Parameter outside of expected range");
}
int i = (int)(pallet.length * x);
int max = pallet.length-1;
int index = i<0?0:i>max?max:i;
int pix = pallet[index] & 0x00ffffff | (0x64 << 24);
return new Color(pix, true);
}
#Override public void paintDeterminate(Graphics g, JComponent c) {
if (!(g instanceof Graphics2D)) {
return;
}
Insets b = progressBar.getInsets(); // area for border
int barRectWidth = progressBar.getWidth() - (b.right + b.left);
int barRectHeight = progressBar.getHeight() - (b.top + b.bottom);
if (barRectWidth <= 0 || barRectHeight <= 0) {
return;
}
int cellLength = getCellLength();
int cellSpacing = getCellSpacing();
// amount of progress to draw
int amountFull = getAmountFull(b, barRectWidth, barRectHeight);
if(progressBar.getOrientation() == JProgressBar.HORIZONTAL) {
// draw the cells
float x = amountFull / (float)barRectWidth;
g.setColor(getColorFromPallet(pallet, x));
g.fillRect(b.left, b.top, amountFull, barRectHeight);
} else { // VERTICAL
//...
}
// Deal with possible text painting
if(progressBar.isStringPainted()) {
paintString(g, b.left, b.top, barRectWidth, barRectHeight, amountFull, b);
}
}
}

You can do either of this way:
UIManager.put("ProgressBar.background", Color.BLACK); //colour of the background
UIManager.put("ProgressBar.foreground", Color.RED); //colour of progress bar
UIManager.put("ProgressBar.selectionBackground",Color.YELLOW); //colour of percentage counter on black background
UIManager.put("ProgressBar.selectionForeground",Color.BLUE); //colour of precentage counter on red background
There is a thread here.
Setting the colors of a JProgressBar text
There is a sample demo here at this link as well.
how to change color progressbar

As you noticed, is value-dependent "progress" painting not supported. Theoretically, setting the foregound of the progressBar instance dynamically (that is depending on value) is the way to go.
But: the details of progressBar painting are highly LAF dependent
in simple LAFs (like Metal) setting the foreground is fine
in fully skinnable LAFs (like synth-based LAF) that support per-instance skinning (f.i. Nimbus) you can try to provide a Painter which uses the foreground
in not-skinnable LAFs there is no way (except overriding the concrete LAF and try to hook into the painting code)
Some snippet:
final JProgressBar bar = new JProgressBar();
// used for Nimbus (beware: just a proof-of-concept - this looks extremely ugly!)
Painter p = new Painter() {
#Override
public void paint(Graphics2D g, Object object, int width, int height) {
JProgressBar bar = (JProgressBar) object;
g.setColor(bar.getForeground());
g.fillRect(0, 0, width, height);
}
};
// install custom painter on the bar
UIDefaults properties = new UIDefaults();
properties.put("ProgressBar[Enabled].foregroundPainter", p);
bar.putClientProperty("Nimbus.Overrides", properties);
// simulate progress
Action action = new AbstractAction("timer") {
#Override
public void actionPerformed(ActionEvent e) {
bar.setValue(bar.getValue() + 1);
// change foreground value-dependent
if (bar.getValue() > 10) {
bar.setForeground(Color.RED);
}
}
};
Timer timer = new Timer(100, action);
timer.start();

Thanks for the help but by trial and error I found a way to get the colour changing with using my existing code.
I am not sure if its the quirk of JPrgressbar but if call do the following call the foreground bar colour can be changed dynamically with minimal rework:
progressBar.setStringPainted(true);

Related

Delayed "Typing Effect" Title [duplicate]

How can I implement Marquee effect in Java Swing
Here's an example using javax.swing.Timer.
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
/** #see http://stackoverflow.com/questions/3617326 */
public class MarqueeTest {
private void display() {
JFrame f = new JFrame("MarqueeTest");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
String s = "Tomorrow, and tomorrow, and tomorrow, "
+ "creeps in this petty pace from day to day, "
+ "to the last syllable of recorded time; ... "
+ "It is a tale told by an idiot, full of "
+ "sound and fury signifying nothing.";
MarqueePanel mp = new MarqueePanel(s, 32);
f.add(mp);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
mp.start();
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new MarqueeTest().display();
}
});
}
}
/** Side-scroll n characters of s. */
class MarqueePanel extends JPanel implements ActionListener {
private static final int RATE = 12;
private final Timer timer = new Timer(1000 / RATE, this);
private final JLabel label = new JLabel();
private final String s;
private final int n;
private int index;
public MarqueePanel(String s, int n) {
if (s == null || n < 1) {
throw new IllegalArgumentException("Null string or n < 1");
}
StringBuilder sb = new StringBuilder(n);
for (int i = 0; i < n; i++) {
sb.append(' ');
}
this.s = sb + s + sb;
this.n = n;
label.setFont(new Font("Serif", Font.ITALIC, 36));
label.setText(sb.toString());
this.add(label);
}
public void start() {
timer.start();
}
public void stop() {
timer.stop();
}
#Override
public void actionPerformed(ActionEvent e) {
index++;
if (index > s.length() - n) {
index = 0;
}
label.setText(s.substring(index, index + n));
}
}
I know this is a late answer, but I just saw another question about a marquee that was closed because it was considered a duplicate of this answer.
So I thought I'd add my suggestion which takes a approach different from the other answers suggested here.
The MarqueePanel scrolls components on a panel not just text. So this allows you to take full advantage of any Swing component. A simple marquee can be used by adding a JLabel with text. A fancier marquee might use a JLabel with HTML so you can use different fonts and color for the text. You can even add a second component with an image.
Basic answer is you draw your text / graphic into a bitmap and then implement a component that paints the bitmap offset by some amount. Usually marquees / tickers scroll left so the offset increases which means the bitmap is painted at -offset. Your component runs a timer that fires periodically, incrementing the offset and invalidating itself so it repaints.
Things like wrapping are a little more complex to deal with but fairly straightforward. If the offset exceeds the bitmap width you reset it back to 0. If the offset + component width > bitmap width you paint the remainder of the component starting from the beginning of the bitmap.
The key to a decent ticker is to make the scrolling as smooth and as flicker free as possible. Therefore it may be necessary to consider double buffering the result, first painting the scrolling bit into a bitmap and then rendering that in one go rather than painting straight into the screen.
Here is some code that I threw together to get you started. I normally would take the ActionListener code and put that in some sort of MarqueeController class to keep this logic separate from the panel, but that's a different question about organizing the MVC architecture, and in a simple enough class like this it may not be so important.
There are also various animation libraries that would help you do this, but I don't normally like to include libraries into projects only to solve one problem like this.
public class MarqueePanel extends JPanel {
private JLabel textLabel;
private int panelLocation;
private ActionListener taskPerformer;
private boolean isRunning = false;
public static final int FRAMES_PER_SECOND = 24;
public static final int MOVEMENT_PER_FRAME = 5;
/**
* Class constructor creates a marquee panel.
*/
public MarqueePanel() {
this.setLayout(null);
this.textLabel = new JLabel("Scrolling Text Here");
this.panelLocation = 0;
this.taskPerformer = new ActionListener() {
public void actionPerformed(ActionEvent evt) {
MarqueePanel.this.tickAnimation();
}
}
}
/**
* Starts the animation.
*/
public void start() {
this.isRunning = true;
this.tickAnimation();
}
/**
* Stops the animation.
*/
public void stop() {
this.isRunning = false;
}
/**
* Moves the label one frame to the left. If it's out of display range, move it back
* to the right, out of display range.
*/
private void tickAnimation() {
this.panelLocation -= MarqueePanel.MOVEMENT_PER_FRAME;
if (this.panelLocation < this.textLabel.getWidth())
this.panelLocaton = this.getWidth();
this.textLabel.setLocation(this.panelLocation, 0);
this.repaint();
if (this.isRunning) {
Timer t = new Timer(1000 / MarqueePanel.FRAMES_PER_SECOND, this.taskPerformer);
t.setRepeats(false);
t.start();
}
}
}
Add a JLabel to your frame or panel.
ScrollText s= new ScrollText("ello Everyone.");
jLabel3.add(s);
public class ScrollText extends JComponent {
private BufferedImage image;
private Dimension imageSize;
private volatile int currOffset;
private Thread internalThread;
private volatile boolean noStopRequested;
public ScrollText(String text) {
currOffset = 0;
buildImage(text);
setMinimumSize(imageSize);
setPreferredSize(imageSize);
setMaximumSize(imageSize);
setSize(imageSize);
noStopRequested = true;
Runnable r = new Runnable() {
public void run() {
try {
runWork();
} catch (Exception x) {
x.printStackTrace();
}
}
};
internalThread = new Thread(r, "ScrollText");
internalThread.start();
}
private void buildImage(String text) {
RenderingHints renderHints = new RenderingHints(
RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
renderHints.put(RenderingHints.KEY_RENDERING,
RenderingHints.VALUE_RENDER_QUALITY);
BufferedImage scratchImage = new BufferedImage(1, 1,
BufferedImage.TYPE_INT_RGB);
Graphics2D scratchG2 = scratchImage.createGraphics();
scratchG2.setRenderingHints(renderHints);
Font font = new Font("Serif", Font.BOLD | Font.ITALIC, 24);
FontRenderContext frc = scratchG2.getFontRenderContext();
TextLayout tl = new TextLayout(text, font, frc);
Rectangle2D textBounds = tl.getBounds();
int textWidth = (int) Math.ceil(textBounds.getWidth());
int textHeight = (int) Math.ceil(textBounds.getHeight());
int horizontalPad = 600;
int verticalPad = 10;
imageSize = new Dimension(textWidth + horizontalPad, textHeight
+ verticalPad);
image = new BufferedImage(imageSize.width, imageSize.height,
BufferedImage.TYPE_INT_RGB);
Graphics2D g2 = image.createGraphics();
g2.setRenderingHints(renderHints);
int baselineOffset = (verticalPad / 2) - ((int) textBounds.getY());
g2.setColor(Color.BLACK);
g2.fillRect(0, 0, imageSize.width, imageSize.height);
g2.setColor(Color.GREEN);
tl.draw(g2, 0, baselineOffset);
// Free-up resources right away, but keep "image" for
// animation.
scratchG2.dispose();
scratchImage.flush();
g2.dispose();
}
public void paint(Graphics g) {
// Make sure to clip the edges, regardless of curr size
g.setClip(0, 0, imageSize.width, imageSize.height);
int localOffset = currOffset; // in case it changes
g.drawImage(image, -localOffset, 0, this);
g.drawImage(image, imageSize.width - localOffset, 0, this);
// draw outline
g.setColor(Color.black);
g.drawRect(0, 0, imageSize.width - 1, imageSize.height - 1);
}
private void runWork() {
while (noStopRequested) {
try {
Thread.sleep(10); // 10 frames per second
// adjust the scroll position
currOffset = (currOffset + 1) % imageSize.width;
// signal the event thread to call paint()
repaint();
} catch (InterruptedException x) {
Thread.currentThread().interrupt();
}
}
}
public void stopRequest() {
noStopRequested = false;
internalThread.interrupt();
}
public boolean isAlive() {
return internalThread.isAlive();
}
}
This is supposed to be an improvement of #camickr MarqueePanel. Please see above.
To map mouse events to the specific components added to MarqueePanel
Override add(Component comp) of MarqueePanel in order to direct all mouse events of the components
An issue here is what do do with the MouseEvents fired from the individual components.
My approach is to remove the mouse listeners form the components added and let the MarqueePanel redirect the event to the correct component.
In my case these components are supposed to be links.
#Override
public Component add(Component comp) {
comp = super.add(comp);
if(comp instanceof MouseListener)
comp.removeMouseListener((MouseListener)comp);
comp.addMouseListener(this);
return comp;
}
Then map the component x to a MarqueePanel x and finally the correct component
#Override
public void mouseClicked(MouseEvent e)
{
Component source = (Component)e.getSource();
int x = source.getX() + e.getX();
int y = source.getY();
MarqueePanel2 marqueePanel = (MarqueePanel2) ((JComponent)e.getSource()).getParent();
double x2 = marqueePanel.getWidth();
double x1 = Math.abs(marqueePanel.scrollOffset);
if(x >= x1 && x <= x2)
{
System.out.println("Bang " + x1);
Component componentAt = getComponentAt(x+marqueePanel.scrollOffset, y);
if(comp instanceof MouseListener)
((MouseListener) componentAt).mouseClicked(e);
System.out.println(componentAt.getName());
}
else
{
return;
}
//System.out.println(x);
}

Java: I need help to moove JLabel with keyboards [duplicate]

I'm new to Java graphics and threads, and I'm trying to make a game (specifically, Pong). The idea is that two people can play on the same keyboard (i.e. there are two paddles that are controlled through different keys). Currently, both players can't move their paddle at the same time.
Is there a solution to this? Are separate threads the answer?
If possible, I'd like the paddles to be able to move (at least seemingly) at the same time.
Update: It seems like using a Set<Integer> to store pressed keys is the best option. I've done that (and it works), but I'm wondering if any of this code is not on the Event Dispatching Thread (EDT) and if I need to use SwingUtilities.invokeLater();. Here's the necessary code:
private Set<Integer> keysDown = Collections.synchronizedSet(new HashSet<Integer>());
public void keyPressed(KeyEvent e)
{
keysDown.add(e.getKeyCode());
}
public void keyReleased(KeyEvent e)
{
keysDown.remove(e.getKeyCode());
}
public void updatePaddlePositions()
{
if (keysDown.contains(KeyEvent.VK_W))
paddleOne.move(-PADDLE_MOVE_INCREMENT);
if (keysDown.contains(KeyEvent.VK_S))
paddleOne.move(PADDLE_MOVE_INCREMENT);
if (keysDown.contains(KeyEvent.VK_UP))
paddleTwo.move(-PADDLE_MOVE_INCREMENT);
if (keysDown.contains(KeyEvent.VK_DOWN))
paddleTwo.move(PADDLE_MOVE_INCREMENT);
try {
Thread.sleep(DELAY);
} catch (InterruptedException e) {
System.out.println("You Interrupted the game!");
}
canvas.repaint();
}
Here's the paintComponent method of the canvas object:
public void paintComponent(Graphics g)
{
super.paintComponent(g);
paddleOne.paint(g);
paddleTwo.paint(g);
updatePaddlePositions(); // Does this need to be SwingUtilities.invokeLater(this)?
// And should updatePaddlePositions() be run() as a result?
}
And here's the paint method of the paddleOne and paddleTwo objects:
public void paint(Graphics g)
{
g.setColor(Color.BLACK);
g.fillRect(x, y, PADDLE_WIDTH, PADDLE_HEIGHT);
}
Feel free to comment on my design if anything pops out as "bad" to you. Lastly, what does Collections.synchronizedSet(new HashSet<Integer>()) mean/do?
Update #2: It seems like key bindings are the way to go (even though they take more code, in this case). What make key bindings better? Is it that they work separately from everything else (and don't need window focus like key listeners)?
Also, I understand that HashSet<Integer> is just a subclass of Set<Integer>, but what is the purpose of Collections.synchronizedSet(...)? I'm assuming it has to do with threads, but I don't know why it's needed in this program (if it is at all).
First off, use Swing KeyBindings Also I see no real need for multi-threadung unless you want your game multi-threaded.
To clarify the problem is you need to be able for 2 players to press different keys?
If so:
Solution:
Simply use booleans to flag whether or not a key is pressed down, you would than of course have to reset the flag when the key is released.
In your game logic you would check the states if the booleans and act appropriately.
See my below example (both paddles can move independently using W and S and UP and DOWN keys):
public class GameLogic {
public GameLogic() {
initComponents();
}
private void initComponents() {
JFrame frame = new JFrame("Game Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//create the gamepanel
final GamePanel gp = new GamePanel(600, 500);
//create panel to hold buttons to start pause and stop the game
JPanel buttonPanel = new JPanel();
buttonPanel.setOpaque(false);
final JButton startButton = new JButton("Start");
final JButton pauseButton = new JButton("Pause");
final JButton stopButton = new JButton("Stop");
pauseButton.setEnabled(false);
stopButton.setEnabled(false);
//add listeners to buttons (most of the actions - excuse the pun - takes palce here :)
startButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
//clear enitites currently in array
gp.clearEntities();
ArrayList<BufferedImage> ballEntityImages = new ArrayList<>();
ArrayList<Long> ballEntityTimings = new ArrayList<>();
ballEntityImages.add(createColouredImage("white", 50, 50, true));
ballEntityTimings.add(500l);
Entity ballEntity = new Entity(gp.getWidth() / 2, gp.getHeight() / 2, ballEntityImages, ballEntityTimings);
ballEntity.RIGHT = true;
//create images for entities
ArrayList<BufferedImage> advEntityImages = new ArrayList<>();
ArrayList<Long> advEntityTimings = new ArrayList<>();
advEntityImages.add(createColouredImage("orange", 10, 100, false));
advEntityTimings.add(500l);
advEntityImages.add(createColouredImage("blue", 10, 100, false));
advEntityTimings.add(500l);
//create entities
AdvancedSpritesEntity player1Entity = new AdvancedSpritesEntity(0, 100, advEntityImages, advEntityTimings);
ArrayList<BufferedImage> entityImages = new ArrayList<>();
ArrayList<Long> entityTimings = new ArrayList<>();
entityImages.add(createColouredImage("red", 10, 100, false));
entityTimings.add(500l);//as its the only image it doesnt really matter what time we put we could use 0l
//entityImages.add(createColouredImage("magenta", 100, 100));
//entityTimings.add(500l);
Entity player2Entity = new Entity(gp.getWidth() - 10, 200, entityImages, entityTimings);
gp.addEntity(player1Entity);
gp.addEntity(player2Entity);//just a standing still Entity for testing
gp.addEntity(ballEntity);//just a standing still Entity for testing
//create Keybingings for gamepanel
GameKeyBindings gameKeyBindings = new GameKeyBindings(gp, player1Entity, player2Entity);
GamePanel.running.set(true);
//start the game loop which will repaint the screen
runGameLoop(gp);
startButton.setEnabled(false);
pauseButton.setEnabled(true);
stopButton.setEnabled(true);
}
});
pauseButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
boolean b = GamePanel.paused.get();
GamePanel.paused.set(!b);//set it to the opposite of what it was i.e paused to unpaused and vice versa
if (pauseButton.getText().equals("Pause")) {
pauseButton.setText("Un-pause");
} else {
pauseButton.setText("Pause");
}
}
});
stopButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
GamePanel.running.set(false);
GamePanel.paused.set(false);
/* if we want enitites to be cleared and a blank panel shown
gp.clearEntities();
gp.repaint();
*/
if (!pauseButton.getText().equals("Pause")) {
pauseButton.setText("Pause");
}
startButton.setEnabled(true);
pauseButton.setEnabled(false);
stopButton.setEnabled(false);
}
});
//add buttons to panel
buttonPanel.add(startButton);
buttonPanel.add(pauseButton);
buttonPanel.add(stopButton);
//add gamepanel to jframe and button panel
frame.add(gp);
frame.add(buttonPanel, BorderLayout.SOUTH);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
//Simply used for testing (to simulate sprites) can create different colored images
public static BufferedImage createColouredImage(String color, int w, int h, boolean circular) {
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = img.createGraphics();
switch (color.toLowerCase()) {
case "green":
g2.setColor(Color.GREEN);
break;
case "magenta":
g2.setColor(Color.MAGENTA);
break;
case "red":
g2.setColor(Color.RED);
break;
case "yellow":
g2.setColor(Color.YELLOW);
break;
case "blue":
g2.setColor(Color.BLUE);
break;
case "orange":
g2.setColor(Color.ORANGE);
break;
case "cyan":
g2.setColor(Color.CYAN);
break;
case "gray":
g2.setColor(Color.GRAY);
break;
default:
g2.setColor(Color.WHITE);
break;
}
if (!circular) {
g2.fillRect(0, 0, img.getWidth(), img.getHeight());
} else {
g2.fillOval(0, 0, img.getWidth(), img.getHeight());
}
g2.dispose();
return img;
}
//Starts a new thread and runs the game loop in it.
private void runGameLoop(final GamePanel gp) {
Thread loop = new Thread(new Runnable() {
#Override
public void run() {
gp.gameLoop();
}
});
loop.start();
}
//Code starts here
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
try {//attempt to set look and feel to nimbus Java 7 and up
for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
GameLogic gameLogic = new GameLogic();
}
});
}
}
class GameKeyBindings {
public GameKeyBindings(JComponent gp, final Entity entity, final Entity entity2) {
gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), "W pressed");
gp.getActionMap().put("W pressed", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent ae) {
entity.UP = true;
}
});
gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), "W released");
gp.getActionMap().put("W released", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent ae) {
entity.UP = false;
}
});
gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), "S pressed");
gp.getActionMap().put("S pressed", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent ae) {
entity.DOWN = true;
}
});
gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, true), "S released");
gp.getActionMap().put("S released", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent ae) {
entity.DOWN = false;
}
});
gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), "down pressed");
gp.getActionMap().put("down pressed", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent ae) {
entity2.DOWN = true;
}
});
gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true), "down released");
gp.getActionMap().put("down released", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent ae) {
entity2.DOWN = false;
}
});
gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false), "up pressed");
gp.getActionMap().put("up pressed", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent ae) {
entity2.UP = true;
}
});
gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true), "up released");
gp.getActionMap().put("up released", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent ae) {
entity2.UP = false;
}
});
}
}
class AdvancedSpritesEntity extends Entity {
public AdvancedSpritesEntity(int x, int y, ArrayList<BufferedImage> images, ArrayList<Long> timings) {
super(x, y, images, timings);
}
void setAnimation(ArrayList<BufferedImage> images, ArrayList<Long> timings) {
reset();//reset variables of animator class
setFrames(images, timings);//set new frames for animation
}
}
class Entity extends Animator {
private int speed = 5;
public boolean UP = false, DOWN = false, LEFT = false, RIGHT = false, visible;
private Rectangle2D.Double rect;
public Entity(int x, int y, ArrayList<BufferedImage> images, ArrayList<Long> timings) {
super(images, timings);
UP = false;
DOWN = false;
LEFT = false;
RIGHT = false;
visible = true;
rect = new Rectangle2D.Double(x, y, getCurrentImage().getWidth(), getCurrentImage().getHeight());
}
public void setSpeed(int speed) {
this.speed = speed;
}
public int getSpeed() {
return speed;
}
public boolean isVisible() {
return visible;
}
public void setVisible(boolean visible) {
this.visible = visible;
}
public HashSet<String> getMask(Entity e) {
HashSet<String> mask = new HashSet<>();
int pixel, a;
BufferedImage bi = e.getCurrentImage();//gets the current image being shown
for (int i = 0; i < bi.getWidth(); i++) { // for every (x,y) component in the given box,
for (int j = 0; j < bi.getHeight(); j++) {
pixel = bi.getRGB(i, j); // get the RGB value of the pixel
a = (pixel >> 24) & 0xff;
if (a != 0) { // if the alpha is not 0, it must be something other than transparent
mask.add((e.getX() + i) + "," + (e.getY() - j)); // add the absolute x and absolute y coordinates to our set
}
}
}
return mask; //return our set
}
// Returns true if there is a collision between object a and object b
public boolean checkPerPixelCollision(Entity b) {
// This method detects to see if the images overlap at all. If they do, collision is possible
int ax1 = (int) getX();
int ay1 = (int) getY();
int ax2 = ax1 + (int) getWidth();
int ay2 = ay1 + (int) getHeight();
int bx1 = (int) b.getX();
int by1 = (int) b.getY();
int bx2 = bx1 + (int) b.getWidth();
int by2 = by1 + (int) b.getHeight();
if (by2 < ay1 || ay2 < by1 || bx2 < ax1 || ax2 < bx1) {
return false; // Collision is impossible.
} else { // Collision is possible.
// get the masks for both images
HashSet<String> maskPlayer1 = getMask(this);
HashSet<String> maskPlayer2 = getMask(b);
maskPlayer1.retainAll(maskPlayer2); // Check to see if any pixels in maskPlayer2 are the same as those in maskPlayer1
if (maskPlayer1.size() > 0) { // if so, than there exists at least one pixel that is the same in both images, thus
return true;
}
}
return false;
}
public void move() {
if (UP) {
rect.y -= speed;
}
if (DOWN) {
rect.y += speed;
}
if (LEFT) {
rect.x -= speed;
}
if (RIGHT) {
rect.x += speed;
}
}
#Override
public void update(long elapsedTime) {
super.update(elapsedTime);
getWidth();//set the rectangles height accordingly after image update
getHeight();//set rectangles height accordingle after update
}
public boolean intersects(Entity e) {
return rect.intersects(e.rect);
}
public double getX() {
return rect.x;
}
public double getY() {
return rect.y;
}
public double getWidth() {
if (getCurrentImage() == null) {//there might be no image (which is unwanted ofcourse but we must not get NPE so we check for null and return 0
return rect.width = 0;
}
return rect.width = getCurrentImage().getWidth();
}
public double getHeight() {
if (getCurrentImage() == null) {
return rect.height = 0;
}
return rect.height = getCurrentImage().getHeight();
}
}
class Animator {
private ArrayList<BufferedImage> frames;
private ArrayList<Long> timings;
private int currIndex;
private long animationTime;
private long totalAnimationDuration;
private AtomicBoolean done;//used to keep track if a single set of frames/ an animtion has finished its loop
public Animator(ArrayList<BufferedImage> frames, ArrayList< Long> timings) {
currIndex = 0;
animationTime = 0;
totalAnimationDuration = 0;
done = new AtomicBoolean(false);
this.frames = new ArrayList<>();
this.timings = new ArrayList<>();
setFrames(frames, timings);
}
public boolean isDone() {
return done.get();
}
public void reset() {
totalAnimationDuration = 0;
done.getAndSet(false);
}
public void update(long elapsedTime) {
if (frames.size() > 1) {
animationTime += elapsedTime;
if (animationTime >= totalAnimationDuration) {
animationTime = animationTime % totalAnimationDuration;
currIndex = 0;
done.getAndSet(true);
}
while (animationTime > timings.get(currIndex)) {
currIndex++;
}
}
}
public BufferedImage getCurrentImage() {
if (frames.isEmpty()) {
return null;
} else {
try {
return frames.get(currIndex);
} catch (Exception ex) {//images might have been altered so we reset the index and return first image of the new frames/animation
currIndex = 0;
return frames.get(currIndex);
}
}
}
public void setFrames(ArrayList<BufferedImage> frames, ArrayList< Long> timings) {
if (frames == null || timings == null) {//so that constructor super(null,null) cause this to throw NullPointerException
return;
}
this.frames = frames;
this.timings.clear();
for (long animTime : timings) {
totalAnimationDuration += animTime;
this.timings.add(totalAnimationDuration);
}
}
}
class GamePanel extends JPanel {
private final static RenderingHints textRenderHints = new RenderingHints(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
private final static RenderingHints imageRenderHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
private final static RenderingHints colorRenderHints = new RenderingHints(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
private final static RenderingHints interpolationRenderHints = new RenderingHints(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
private final static RenderingHints renderHints = new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
public static AtomicBoolean running = new AtomicBoolean(false), paused = new AtomicBoolean(false);
private int width, height, frameCount = 0, fps = 0;
private ArrayList<Entity> entities = new ArrayList<>();
private final Random random = new Random();
public GamePanel(int w, int h) {
super(true);//make sure double buffering is enabled
setIgnoreRepaint(true);//mustnt repaint itself the gameloop will do that
width = w;
height = h;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(width, height);
}
public void addEntity(Entity e) {
entities.add(e);
}
void clearEntities() {
entities.clear();
}
//Only run this in another Thread!
public void gameLoop() {
//This value would probably be stored elsewhere.
final double GAME_HERTZ = 30.0;
//Calculate how many ns each frame should take for our target game hertz.
final double TIME_BETWEEN_UPDATES = 1000000000 / GAME_HERTZ;
//At the very most we will update the game this many times before a new render.
//If you're worried about visual hitches more than perfect timing, set this to 1.
final int MAX_UPDATES_BEFORE_RENDER = 5;
//We will need the last update time.
double lastUpdateTime = System.nanoTime();
//Store the last time we rendered.
double lastRenderTime = System.nanoTime();
//If we are able to get as high as this FPS, don't render again.
final double TARGET_FPS = 60;
final double TARGET_TIME_BETWEEN_RENDERS = 1000000000 / TARGET_FPS;
//Simple way of finding FPS.
int lastSecondTime = (int) (lastUpdateTime / 1000000000);
//store the time we started this will be used for updating map and charcter animations
long currTime = System.currentTimeMillis();
while (running.get()) {
if (!paused.get()) {
double now = System.nanoTime();
long elapsedTime = System.currentTimeMillis() - currTime;
currTime += elapsedTime;
int updateCount = 0;
//Do as many game updates as we need to, potentially playing catchup.
while (now - lastUpdateTime > TIME_BETWEEN_UPDATES && updateCount < MAX_UPDATES_BEFORE_RENDER) {
updateGame(elapsedTime);//Update the entity movements and collision checks etc (all has to do with updating the games status i.e call move() on Enitites)
lastUpdateTime += TIME_BETWEEN_UPDATES;
updateCount++;
}
//If for some reason an update takes forever, we don't want to do an insane number of catchups.
//If you were doing some sort of game that needed to keep EXACT time, you would get rid of this.
if (now - lastUpdateTime > TIME_BETWEEN_UPDATES) {
lastUpdateTime = now - TIME_BETWEEN_UPDATES;
}
drawGame();//draw the game by invokeing repaint (which will call paintComponent) on this JPanel
lastRenderTime = now;
//Update the frames we got.
int thisSecond = (int) (lastUpdateTime / 1000000000);
if (thisSecond > lastSecondTime) {
//System.out.println("NEW SECOND " + thisSecond + " " + frameCount);
fps = frameCount;
frameCount = 0;
lastSecondTime = thisSecond;
}
//Yield until it has been at least the target time between renders. This saves the CPU from hogging.
while (now - lastRenderTime < TARGET_TIME_BETWEEN_RENDERS && now - lastUpdateTime < TIME_BETWEEN_UPDATES) {
//allow the threading system to play threads that are waiting to run.
Thread.yield();
//This stops the app from consuming all your CPU. It makes this slightly less accurate, but is worth it.
//You can remove this line and it will still work (better), your CPU just climbs on certain OSes.
//FYI on some OS's this can cause pretty bad stuttering. Scroll down and have a look at different peoples' solutions to this.
//On my OS(Windows 7 x64 intel i3) it does not allow for time to make enityt etc move thus all stands still
try {
Thread.sleep(1);
} catch (Exception e) {
}
now = System.nanoTime();
}
}
}
fps = 0;//no more running set fps to 0
}
private void drawGame() {
//Both revalidate and repaint are thread-safe — you need not invoke them from the event-dispatching thread. http://docs.oracle.com/javase/tutorial/uiswing/layout/howLayoutWorks.html
repaint();
}
private void updateGame(long elapsedTime) {
updateEntityMovements(elapsedTime);
checkForCollisions();
}
private void checkForCollisions() {
if (entities.get(0).intersects(entities.get(2)) || entities.get(1).intersects(entities.get(2))) {
if (entities.get(2).LEFT) {
entities.get(2).RIGHT = true;
entities.get(2).LEFT = false;
} else {
entities.get(2).LEFT = true;
entities.get(2).RIGHT = false;
}
System.out.println("Intersecting");
} /*
//This is best used when images have transparent and non-transparent pixels (only detects pixel collisions of non-transparent pixels)
if (entities.get(0).checkPerPixelCollision(entities.get(2)) | entities.get(1).checkPerPixelCollision(entities.get(2))) {
if (entities.get(2).LEFT) {
entities.get(2).RIGHT = true;
entities.get(2).LEFT = false;
} else {
entities.get(2).LEFT = true;
entities.get(2).RIGHT = false;
}
System.out.println("Intersecting");
}
*/
}
private void updateEntityMovements(long elapsedTime) {
for (Entity e : entities) {
e.update(elapsedTime);
e.move();
}
}
#Override
protected void paintComponent(Graphics grphcs) {
super.paintComponent(grphcs);
Graphics2D g2d = (Graphics2D) grphcs;
applyRenderHints(g2d);
drawBackground(g2d);
drawEntitiesToScreen(g2d);
drawFpsCounter(g2d);
frameCount++;
}
public static void applyRenderHints(Graphics2D g2d) {
g2d.setRenderingHints(textRenderHints);
g2d.setRenderingHints(imageRenderHints);
g2d.setRenderingHints(colorRenderHints);
g2d.setRenderingHints(interpolationRenderHints);
g2d.setRenderingHints(renderHints);
}
private void drawEntitiesToScreen(Graphics2D g2d) {
for (Entity e : entities) {
if (e.isVisible()) {
g2d.drawImage(e.getCurrentImage(), (int) e.getX(), (int) e.getY(), null);
}
}
}
private void drawFpsCounter(Graphics2D g2d) {
g2d.setColor(Color.WHITE);
g2d.drawString("FPS: " + fps, 5, 10);
}
private void drawBackground(Graphics2D g2d) {
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, getWidth(), getHeight());
//thanks to trashgod for lovely testing background :) http://stackoverflow.com/questions/3256269/jtextfields-on-top-of-active-drawing-on-jpanel-threading-problems/3256941#3256941
g2d.setColor(Color.BLACK);
for (int i = 0; i < 128; i++) {
g2d.setColor(new Color(random.nextInt(256), random.nextInt(256), random.nextInt(256)));//random color
g2d.drawLine(getWidth() / 2, getHeight() / 2, random.nextInt(getWidth()), random.nextInt(getHeight()));
}
}
}
This may be a legitimate use case for the low-level access afforded to a KeyListener. See How to Write a Key Listener and KeyEventDemo; don't forget to requestFocusInWindow(). Also consider offering keyboard control for one player and mouse control for the other.
Addendum: #David Kroukamp has adduced an appealing counter-example using key bindings. For reference, the example cited here illustrates one approach to managing key preferences.

CardLayout showing two panels, flashing

I'm trying to use CardLayout to show two JPanels, a main menu, and a controls screen. When I add two JPanels to my cards JPanel, it just shows two with flashing images. Here is my code:
package main;
public class MazeGame {
// Layout
public static JPanel cards = new JPanel();
// Window
public static JFrame window;
public static String windowLabel = "2D Maze Game - Before Alpha";
// Window Dimensions and Location
public static int WIDTH = 600;
public static int HEIGHT = 600;
public static Component center = null;
public static int exit = 3;
public static void main(String[] args) {
window = new JFrame(windowLabel);
window.setSize(new Dimension(WIDTH, HEIGHT));
window.setResizable(false);
window.setLocationRelativeTo(center);
window.setDefaultCloseOperation(exit);
cards.setLayout(new CardLayout());
cards.add(new MazeGamePanel(), "main");
cards.add(new MazeControlsPanel(), "controls");
window.add(cards);
CardLayout cl = (CardLayout) cards.getLayout();
cl.show(cards, "main");
window.setVisible(true);
}
}
MazeGamePanel:
public class MazeGamePanel extends JPanel implements Runnable {
private static final long serialVersionUID = 1L;
// Timer
public Timer timer;
// Font
public Font bitTrip;
public Thread thread;
public BufferedImage canvas;
public Graphics2D g;
public boolean running;
public int HEIGHT = MazeGame.HEIGHT;
public int WIDTH = MazeGame.WIDTH;
public int FPS = 30;
public int opacity = 255;
public int selectedOption = 0;
public String option1 = "Play";
public String option2 = "Controls";
public String option3 = "Quit";
public MazeGamePanel() {
this.setFocusable(true);
this.requestFocus();
addKeyListener(new MazeGameKeyListener());
try {
bitTrip = Font.createFont(Font.TRUETYPE_FONT, new File(
"res/font/BitTrip7.TTF"));
} catch (FontFormatException | IOException e) {
e.printStackTrace();
}
/**
ActionListener action = new ActionListener () {
public void actionPerformed (ActionEvent e) {
if(opacity != 0) {
opacity--;
} else {
timer.stop();
opacity = 0;
}
}
};
timer = new Timer(10, action);
timer.setInitialDelay(0);
timer.start();
*/
}
public void addNotify() {
super.addNotify();
if (thread == null) {
thread = new Thread(this);
thread.start();
}
}
public void run() {
running = true;
canvas = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
g = (Graphics2D) canvas.getGraphics();
long startTime = 0;
long millis = 0;
long waitTime = 0;
long targetTime = 1000 / FPS;
while (running) {
startTime = System.nanoTime();
update();
render();
draw();
millis = (System.nanoTime() - startTime) / 1000000;
waitTime = targetTime - millis;
try {
Thread.sleep(waitTime);
} catch (Exception e) {
}
}
}
// TODO
public void render() {
g.setColor(Color.WHITE);
g.fillRect(0, 0, WIDTH, HEIGHT);
bitTrip = bitTrip.deriveFont(40F);
g.setFont(bitTrip);
if (selectedOption == 0) {
//Play
g.setColor(Color.BLACK);
g.drawString(option1, WIDTH / 2 - 200, HEIGHT / 2);
//Controls
g.setColor(Color.GRAY);
g.drawString(option2, WIDTH / 2 - 200, HEIGHT / 2 + 50);
//Quit
g.setColor(Color.GRAY);
g.drawString(option3, WIDTH / 2 - 200, HEIGHT / 2 + 100);
} else if (selectedOption == 1) {
//Play
g.setColor(Color.GRAY);
g.drawString(option1, WIDTH / 2 - 200, HEIGHT / 2);
//Controls
g.setColor(Color.BLACK);
g.drawString(option2, WIDTH / 2 - 200, HEIGHT / 2 + 50);
//Quit
g.setColor(Color.GRAY);
g.drawString(option3, WIDTH / 2 - 200, HEIGHT / 2 + 100);
} else if (selectedOption == 2) {
//Play
g.setColor(Color.GRAY);
g.drawString(option1, WIDTH / 2 - 200, HEIGHT / 2);
//Controls
g.setColor(Color.GRAY);
g.drawString(option2, WIDTH / 2 - 200, HEIGHT / 2 + 50);
//Quit
g.setColor(Color.BLACK);
g.drawString(option3, WIDTH / 2 - 200, HEIGHT / 2 + 100);
}
//g.setColor(new Color(0, 0, 0, opacity));
//g.fillRect(0, 0, WIDTH, HEIGHT);
}
public void update() {
}
public void draw() {
Graphics g2 = this.getGraphics();
g2.drawImage(canvas, 0, 0, null);
g2.dispose();
}
private class MazeGameKeyListener extends KeyAdapter {
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_UP) {
if (selectedOption == 1) {
selectedOption = 0;
} else if (selectedOption == 2) {
selectedOption = 1;
}
}
if (e.getKeyCode() == KeyEvent.VK_DOWN) {
if (selectedOption == 0) {
selectedOption = 1;
} else if (selectedOption == 1) {
selectedOption = 2;
}
}
if(e.getKeyCode() == KeyEvent.VK_ENTER) {
if(selectedOption == 1) {
MazeGame.window.removeAll();
MazeGame.window.add(new MazeControlsPanel());
MazeGame.window.validate();
}
}
}
}
}
MazeControlsPanel:
package screens;
public class MazeControlsPanel extends JPanel implements Runnable {
private static final long serialVersionUID = 1L;
// Font
public Font bitTrip;
public Thread thread;
public BufferedImage canvas;
public Graphics2D g;
public boolean running;
public int HEIGHT = MazeGame.HEIGHT;
public int WIDTH = MazeGame.WIDTH;
public int FPS = 30;
public int opacity = 255;
public int selectedOption = 0;
public MazeControlsPanel() {
this.setFocusable(true);
this.requestFocus();
addKeyListener(new MazeControlsKeyListener());
try {
bitTrip = Font.createFont(Font.TRUETYPE_FONT, new File(
"res/font/BitTrip7.TTF"));
} catch (FontFormatException | IOException e) {
e.printStackTrace();
}
/**
final Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
public void run() {
if (opacity != 0) {
opacity--;
} else {
timer.cancel();
opacity = 0;
}
}
}, 0, 4);
*/
}
public void addNotify() {
super.addNotify();
if (thread == null) {
thread = new Thread(this);
thread.start();
}
}
public void run() {
running = true;
canvas = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
g = (Graphics2D) canvas.getGraphics();
long startTime = 0;
long millis = 0;
long waitTime = 0;
long targetTime = 1000 / FPS;
while (running) {
startTime = System.nanoTime();
update();
render();
draw();
millis = (System.nanoTime() - startTime) / 1000000;
waitTime = targetTime - millis;
try {
Thread.sleep(waitTime);
} catch (Exception e) {
}
}
}
// TODO
public void render() {
g.setColor(Color.WHITE);
g.fillRect(0, 0, WIDTH, HEIGHT);
bitTrip = bitTrip.deriveFont(40F);
g.setFont(bitTrip);
// Quit
g.setColor(Color.BLACK);
g.drawString("Main Menu", WIDTH / 2 - 200, HEIGHT / 2 + 100);
//g.setColor(new Color(0, 0, 0, opacity));
//g.fillRect(0, 0, WIDTH, HEIGHT);
}
public void update() {
}
public void draw() {
Graphics g2 = this.getGraphics();
g2.drawImage(canvas, 0, 0, null);
g2.dispose();
}
private class MazeControlsKeyListener extends KeyAdapter {
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
}
}
}
}
Here's a problem:
final Timer timer = new Timer();
timer.scheduleAtFixedRate(new TimerTask() {
Don't use a java.util.Timer in a Swing program as you will have threading problems. Instead use a Swing Timer.
Also, you're making Swing calls in background threads and using Graphics object obtained by calling getGraphics() on a component, two no-nos for Swing programs.
EDIT
Here is a program that I worked on that swaps components with a CardLayout, but fades one component out as it fades the other one in. What it does:
The program adds all the swapping components to the CardLayout using JPanel.
It also adds a single SwappingImgPanel, a JPanel created to draw two images, one of the component that is fading out, and one of the component that is fading in.
When you swap components, you create images of the two components, the one currently visible, and the one that will next be visible.
You send the images to the SwappingImgPanel instance
You call swap() on the SwappingImgPanel instance.
The SwappingImgPanel will then draw both images but uses a Swing Timer to change the Graphic object's composite value. This is what causes an image to be partially visible.
When the SwappingImgPanel's Timer is done, a done() method is called which sets the SwappingImgPanel's State to State.DONE.
The main GUI is listening to the SwappingImgPanel's state value, and when it achieves State.DONE, the main GUI shows the actual next component (and not an image of it).
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.HashMap;
import java.util.Map;
import javax.swing.*;
#SuppressWarnings("serial")
public class DimmingPanelSwaps extends JPanel {
private static final int DELTA_TIME = 10;
private static final int ELAPSED_TIME = 3000;
private static final String SWAPPING_IMG_PANEL = "swapping img panel";
private CardLayout cardlayout = new CardLayout();
private JPanel cardHolderPanel = new JPanel(cardlayout);
private DefaultComboBoxModel<String> comboModel = new DefaultComboBoxModel<>();
private JComboBox<String> cardCombo = new JComboBox<>(comboModel);
private Map<String, JComponent> componentMap = new HashMap<String, JComponent>();
private String key = "";
private SwappingImgPanel swappingImgPanel = new SwappingImgPanel(DELTA_TIME, ELAPSED_TIME);
public DimmingPanelSwaps() {
registerComponent(createComponentOne(), "one");
registerComponent(createComponentTwo(), "two");
registerComponent(createComponentThree(), "three");
registerComponent(createComponentFour(), "four");
key = "one";
cardHolderPanel.add(swappingImgPanel, SWAPPING_IMG_PANEL);
JPanel southPanel = new JPanel();
southPanel.add(cardCombo);
setLayout(new BorderLayout());
add(cardHolderPanel, BorderLayout.CENTER);
add(southPanel, BorderLayout.SOUTH);
swappingImgPanel.addPropertyChangeListener(new PropertyChangeListener() {
#Override
public void propertyChange(PropertyChangeEvent pcEvt) {
if (pcEvt.getNewValue() == State.DONE) {
cardlayout.show(cardHolderPanel, key);
cardCombo.setEnabled(true);
}
}
});
cardCombo.addActionListener(new CardComboListener());
}
private JPanel createComponentFour() {
int rows = 4;
int cols = 4;
int gap = 5;
int tfColumns = 8;
JPanel panel = new JPanel(new GridLayout(rows, cols, gap, gap));
for (int i = 0; i < rows * cols; i++) {
JTextField textField = new JTextField(tfColumns);
JPanel tfPanel = new JPanel();
tfPanel.add(textField);
panel.add(tfPanel);
}
return panel;
}
private JLabel createComponentThree() {
int biWidth = 200;
BufferedImage img = new BufferedImage(biWidth, biWidth, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = img.createGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
g2.setPaint(new GradientPaint(0, 0, Color.red, 20, 20, Color.blue, true));
g2.fillOval(0, 0, biWidth, biWidth);
g2.dispose();
Icon icon = new ImageIcon(img);
JLabel label = new JLabel(icon);
return label;
}
private JScrollPane createComponentTwo() {
JTextArea textArea = new JTextArea(15, 40);
JScrollPane scrollpane = new JScrollPane(textArea);
scrollpane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
scrollpane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
return scrollpane;
}
private JPanel createComponentOne() {
JPanel innerPanel = new JPanel(new GridLayout(1, 0, 5, 0));
String[] btnTitles = {"One", "Two", "Three"};
for (String btnTitle : btnTitles) {
JButton btn = new JButton(btnTitle);
innerPanel.add(btn);
}
JPanel panel = new JPanel(new GridBagLayout());
panel.add(innerPanel);
return panel;
}
#SuppressWarnings("hiding")
private void registerComponent(JComponent jComp, String key) {
cardHolderPanel.add(jComp, key);
componentMap.put(key, jComp);
comboModel.addElement(key);
}
private class CardComboListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
final String oldKey = key;
key = (String) cardCombo.getSelectedItem();
cardCombo.setEnabled(false);
final JComponent firstComp = componentMap.get(oldKey);
final BufferedImage firstImg = extractComponentImg(firstComp);
final JComponent secondComp = componentMap.get(key);
final BufferedImage secondImg = extractComponentImg(secondComp);
cardlayout.show(cardHolderPanel, SWAPPING_IMG_PANEL);
swappingImgPanel.setFirstImg(firstImg);
swappingImgPanel.setSecondImg(secondImg);
swappingImgPanel.swap();
}
private BufferedImage extractComponentImg(final JComponent component) {
Dimension size = component.getSize();
BufferedImage img = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = img.createGraphics();
component.paint(g2);
g2.dispose();
return img;
}
}
private static void createAndShowGui() {
DimmingPanelSwaps mainPanel = new DimmingPanelSwaps();
JFrame frame = new JFrame("Dimming Panel Swaps");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
/**
* A JPanel that draws two images
* When swap is called, the first image is shown
* Then a Timer dims the first image while it reveals
* the second image.
* When the elapsed time is complete, it sets its state to State.DONE.
* #author Pete
*
*/
#SuppressWarnings("serial")
class SwappingImgPanel extends JPanel {
public static final String STATE = "state";
private BufferedImage firstImg;
private BufferedImage secondImg;
private int deltaTime;
private int elapsedTime;
// state is a "bound" property, one that is listened to via PropertyChangeSupport
private State state = State.PENDING;
private float alpha1;
private float alpha2;
public SwappingImgPanel(final int deltaTime, final int elapsedTime) {
this.deltaTime = deltaTime;
this.elapsedTime = elapsedTime;
}
public void swap() {
setState(State.STARTED);
if (firstImg == null || secondImg == null) {
done();
}
alpha1 = 1.0f;
alpha2 = 0.0f;
new Timer(deltaTime, new ActionListener() {
private int counter = 0;
private int max = elapsedTime / deltaTime;
#Override
public void actionPerformed(ActionEvent e) {
if (counter >= elapsedTime / deltaTime) {
((Timer)e.getSource()).stop();
done();
return;
}
// set new alpha composite values
alpha1 = ((float)max - counter) / (float) max;
alpha2 = (float) counter / (float) max;
// make sure alphas are within bounds
alpha1 = Math.min(1f, alpha1);
alpha1 = Math.max(0f, alpha1);
alpha2 = Math.min(1f, alpha2);
alpha2 = Math.max(0f, alpha2);
repaint();
counter++;
}
}).start();
}
private void done() {
firstImg = null;
secondImg = null;
setState(State.DONE);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (firstImg == null || secondImg == null) {
return;
}
// create a new Graphics2D object with g.create()
// to avoid any possible side effects from changing the
// composite of the JVM's Graphics object
Graphics2D g2 = (Graphics2D) g.create();
// set the first alpha composite, and draw the image
g2.setComposite(((AlphaComposite)g2.getComposite()).derive(alpha1));
g2.drawImage(firstImg, 0, 0, this);
// set the second alpha composite, and draw the image
g2.setComposite(((AlphaComposite)g2.getComposite()).derive(alpha2));
g2.drawImage(secondImg, 0, 0, this);
g2.dispose(); // can get rid of this Graphics because we created it
}
public void setFirstImg(BufferedImage firstImg) {
this.firstImg = firstImg;
}
public void setSecondImg(BufferedImage secondImg) {
this.secondImg = secondImg;
}
public State getState() {
return state;
}
public void setState(State state) {
State oldValue = this.state;
State newValue = state;
this.state = state;
firePropertyChange(STATE, oldValue, newValue);
}
}
/**
* Modeled on SwingWorker.StateValue
* #author Pete
*
*/
enum State {
PENDING, STARTED, DONE
}

Threads with Key Bindings

I'm new to Java graphics and threads, and I'm trying to make a game (specifically, Pong). The idea is that two people can play on the same keyboard (i.e. there are two paddles that are controlled through different keys). Currently, both players can't move their paddle at the same time.
Is there a solution to this? Are separate threads the answer?
If possible, I'd like the paddles to be able to move (at least seemingly) at the same time.
Update: It seems like using a Set<Integer> to store pressed keys is the best option. I've done that (and it works), but I'm wondering if any of this code is not on the Event Dispatching Thread (EDT) and if I need to use SwingUtilities.invokeLater();. Here's the necessary code:
private Set<Integer> keysDown = Collections.synchronizedSet(new HashSet<Integer>());
public void keyPressed(KeyEvent e)
{
keysDown.add(e.getKeyCode());
}
public void keyReleased(KeyEvent e)
{
keysDown.remove(e.getKeyCode());
}
public void updatePaddlePositions()
{
if (keysDown.contains(KeyEvent.VK_W))
paddleOne.move(-PADDLE_MOVE_INCREMENT);
if (keysDown.contains(KeyEvent.VK_S))
paddleOne.move(PADDLE_MOVE_INCREMENT);
if (keysDown.contains(KeyEvent.VK_UP))
paddleTwo.move(-PADDLE_MOVE_INCREMENT);
if (keysDown.contains(KeyEvent.VK_DOWN))
paddleTwo.move(PADDLE_MOVE_INCREMENT);
try {
Thread.sleep(DELAY);
} catch (InterruptedException e) {
System.out.println("You Interrupted the game!");
}
canvas.repaint();
}
Here's the paintComponent method of the canvas object:
public void paintComponent(Graphics g)
{
super.paintComponent(g);
paddleOne.paint(g);
paddleTwo.paint(g);
updatePaddlePositions(); // Does this need to be SwingUtilities.invokeLater(this)?
// And should updatePaddlePositions() be run() as a result?
}
And here's the paint method of the paddleOne and paddleTwo objects:
public void paint(Graphics g)
{
g.setColor(Color.BLACK);
g.fillRect(x, y, PADDLE_WIDTH, PADDLE_HEIGHT);
}
Feel free to comment on my design if anything pops out as "bad" to you. Lastly, what does Collections.synchronizedSet(new HashSet<Integer>()) mean/do?
Update #2: It seems like key bindings are the way to go (even though they take more code, in this case). What make key bindings better? Is it that they work separately from everything else (and don't need window focus like key listeners)?
Also, I understand that HashSet<Integer> is just a subclass of Set<Integer>, but what is the purpose of Collections.synchronizedSet(...)? I'm assuming it has to do with threads, but I don't know why it's needed in this program (if it is at all).
First off, use Swing KeyBindings Also I see no real need for multi-threadung unless you want your game multi-threaded.
To clarify the problem is you need to be able for 2 players to press different keys?
If so:
Solution:
Simply use booleans to flag whether or not a key is pressed down, you would than of course have to reset the flag when the key is released.
In your game logic you would check the states if the booleans and act appropriately.
See my below example (both paddles can move independently using W and S and UP and DOWN keys):
public class GameLogic {
public GameLogic() {
initComponents();
}
private void initComponents() {
JFrame frame = new JFrame("Game Test");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//create the gamepanel
final GamePanel gp = new GamePanel(600, 500);
//create panel to hold buttons to start pause and stop the game
JPanel buttonPanel = new JPanel();
buttonPanel.setOpaque(false);
final JButton startButton = new JButton("Start");
final JButton pauseButton = new JButton("Pause");
final JButton stopButton = new JButton("Stop");
pauseButton.setEnabled(false);
stopButton.setEnabled(false);
//add listeners to buttons (most of the actions - excuse the pun - takes palce here :)
startButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
//clear enitites currently in array
gp.clearEntities();
ArrayList<BufferedImage> ballEntityImages = new ArrayList<>();
ArrayList<Long> ballEntityTimings = new ArrayList<>();
ballEntityImages.add(createColouredImage("white", 50, 50, true));
ballEntityTimings.add(500l);
Entity ballEntity = new Entity(gp.getWidth() / 2, gp.getHeight() / 2, ballEntityImages, ballEntityTimings);
ballEntity.RIGHT = true;
//create images for entities
ArrayList<BufferedImage> advEntityImages = new ArrayList<>();
ArrayList<Long> advEntityTimings = new ArrayList<>();
advEntityImages.add(createColouredImage("orange", 10, 100, false));
advEntityTimings.add(500l);
advEntityImages.add(createColouredImage("blue", 10, 100, false));
advEntityTimings.add(500l);
//create entities
AdvancedSpritesEntity player1Entity = new AdvancedSpritesEntity(0, 100, advEntityImages, advEntityTimings);
ArrayList<BufferedImage> entityImages = new ArrayList<>();
ArrayList<Long> entityTimings = new ArrayList<>();
entityImages.add(createColouredImage("red", 10, 100, false));
entityTimings.add(500l);//as its the only image it doesnt really matter what time we put we could use 0l
//entityImages.add(createColouredImage("magenta", 100, 100));
//entityTimings.add(500l);
Entity player2Entity = new Entity(gp.getWidth() - 10, 200, entityImages, entityTimings);
gp.addEntity(player1Entity);
gp.addEntity(player2Entity);//just a standing still Entity for testing
gp.addEntity(ballEntity);//just a standing still Entity for testing
//create Keybingings for gamepanel
GameKeyBindings gameKeyBindings = new GameKeyBindings(gp, player1Entity, player2Entity);
GamePanel.running.set(true);
//start the game loop which will repaint the screen
runGameLoop(gp);
startButton.setEnabled(false);
pauseButton.setEnabled(true);
stopButton.setEnabled(true);
}
});
pauseButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
boolean b = GamePanel.paused.get();
GamePanel.paused.set(!b);//set it to the opposite of what it was i.e paused to unpaused and vice versa
if (pauseButton.getText().equals("Pause")) {
pauseButton.setText("Un-pause");
} else {
pauseButton.setText("Pause");
}
}
});
stopButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent ae) {
GamePanel.running.set(false);
GamePanel.paused.set(false);
/* if we want enitites to be cleared and a blank panel shown
gp.clearEntities();
gp.repaint();
*/
if (!pauseButton.getText().equals("Pause")) {
pauseButton.setText("Pause");
}
startButton.setEnabled(true);
pauseButton.setEnabled(false);
stopButton.setEnabled(false);
}
});
//add buttons to panel
buttonPanel.add(startButton);
buttonPanel.add(pauseButton);
buttonPanel.add(stopButton);
//add gamepanel to jframe and button panel
frame.add(gp);
frame.add(buttonPanel, BorderLayout.SOUTH);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
//Simply used for testing (to simulate sprites) can create different colored images
public static BufferedImage createColouredImage(String color, int w, int h, boolean circular) {
BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = img.createGraphics();
switch (color.toLowerCase()) {
case "green":
g2.setColor(Color.GREEN);
break;
case "magenta":
g2.setColor(Color.MAGENTA);
break;
case "red":
g2.setColor(Color.RED);
break;
case "yellow":
g2.setColor(Color.YELLOW);
break;
case "blue":
g2.setColor(Color.BLUE);
break;
case "orange":
g2.setColor(Color.ORANGE);
break;
case "cyan":
g2.setColor(Color.CYAN);
break;
case "gray":
g2.setColor(Color.GRAY);
break;
default:
g2.setColor(Color.WHITE);
break;
}
if (!circular) {
g2.fillRect(0, 0, img.getWidth(), img.getHeight());
} else {
g2.fillOval(0, 0, img.getWidth(), img.getHeight());
}
g2.dispose();
return img;
}
//Starts a new thread and runs the game loop in it.
private void runGameLoop(final GamePanel gp) {
Thread loop = new Thread(new Runnable() {
#Override
public void run() {
gp.gameLoop();
}
});
loop.start();
}
//Code starts here
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
try {//attempt to set look and feel to nimbus Java 7 and up
for (UIManager.LookAndFeelInfo info : UIManager.getInstalledLookAndFeels()) {
if ("Nimbus".equals(info.getName())) {
UIManager.setLookAndFeel(info.getClassName());
break;
}
}
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
GameLogic gameLogic = new GameLogic();
}
});
}
}
class GameKeyBindings {
public GameKeyBindings(JComponent gp, final Entity entity, final Entity entity2) {
gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, false), "W pressed");
gp.getActionMap().put("W pressed", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent ae) {
entity.UP = true;
}
});
gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_W, 0, true), "W released");
gp.getActionMap().put("W released", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent ae) {
entity.UP = false;
}
});
gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, false), "S pressed");
gp.getActionMap().put("S pressed", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent ae) {
entity.DOWN = true;
}
});
gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_S, 0, true), "S released");
gp.getActionMap().put("S released", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent ae) {
entity.DOWN = false;
}
});
gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false), "down pressed");
gp.getActionMap().put("down pressed", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent ae) {
entity2.DOWN = true;
}
});
gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, true), "down released");
gp.getActionMap().put("down released", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent ae) {
entity2.DOWN = false;
}
});
gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false), "up pressed");
gp.getActionMap().put("up pressed", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent ae) {
entity2.UP = true;
}
});
gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, true), "up released");
gp.getActionMap().put("up released", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent ae) {
entity2.UP = false;
}
});
}
}
class AdvancedSpritesEntity extends Entity {
public AdvancedSpritesEntity(int x, int y, ArrayList<BufferedImage> images, ArrayList<Long> timings) {
super(x, y, images, timings);
}
void setAnimation(ArrayList<BufferedImage> images, ArrayList<Long> timings) {
reset();//reset variables of animator class
setFrames(images, timings);//set new frames for animation
}
}
class Entity extends Animator {
private int speed = 5;
public boolean UP = false, DOWN = false, LEFT = false, RIGHT = false, visible;
private Rectangle2D.Double rect;
public Entity(int x, int y, ArrayList<BufferedImage> images, ArrayList<Long> timings) {
super(images, timings);
UP = false;
DOWN = false;
LEFT = false;
RIGHT = false;
visible = true;
rect = new Rectangle2D.Double(x, y, getCurrentImage().getWidth(), getCurrentImage().getHeight());
}
public void setSpeed(int speed) {
this.speed = speed;
}
public int getSpeed() {
return speed;
}
public boolean isVisible() {
return visible;
}
public void setVisible(boolean visible) {
this.visible = visible;
}
public HashSet<String> getMask(Entity e) {
HashSet<String> mask = new HashSet<>();
int pixel, a;
BufferedImage bi = e.getCurrentImage();//gets the current image being shown
for (int i = 0; i < bi.getWidth(); i++) { // for every (x,y) component in the given box,
for (int j = 0; j < bi.getHeight(); j++) {
pixel = bi.getRGB(i, j); // get the RGB value of the pixel
a = (pixel >> 24) & 0xff;
if (a != 0) { // if the alpha is not 0, it must be something other than transparent
mask.add((e.getX() + i) + "," + (e.getY() - j)); // add the absolute x and absolute y coordinates to our set
}
}
}
return mask; //return our set
}
// Returns true if there is a collision between object a and object b
public boolean checkPerPixelCollision(Entity b) {
// This method detects to see if the images overlap at all. If they do, collision is possible
int ax1 = (int) getX();
int ay1 = (int) getY();
int ax2 = ax1 + (int) getWidth();
int ay2 = ay1 + (int) getHeight();
int bx1 = (int) b.getX();
int by1 = (int) b.getY();
int bx2 = bx1 + (int) b.getWidth();
int by2 = by1 + (int) b.getHeight();
if (by2 < ay1 || ay2 < by1 || bx2 < ax1 || ax2 < bx1) {
return false; // Collision is impossible.
} else { // Collision is possible.
// get the masks for both images
HashSet<String> maskPlayer1 = getMask(this);
HashSet<String> maskPlayer2 = getMask(b);
maskPlayer1.retainAll(maskPlayer2); // Check to see if any pixels in maskPlayer2 are the same as those in maskPlayer1
if (maskPlayer1.size() > 0) { // if so, than there exists at least one pixel that is the same in both images, thus
return true;
}
}
return false;
}
public void move() {
if (UP) {
rect.y -= speed;
}
if (DOWN) {
rect.y += speed;
}
if (LEFT) {
rect.x -= speed;
}
if (RIGHT) {
rect.x += speed;
}
}
#Override
public void update(long elapsedTime) {
super.update(elapsedTime);
getWidth();//set the rectangles height accordingly after image update
getHeight();//set rectangles height accordingle after update
}
public boolean intersects(Entity e) {
return rect.intersects(e.rect);
}
public double getX() {
return rect.x;
}
public double getY() {
return rect.y;
}
public double getWidth() {
if (getCurrentImage() == null) {//there might be no image (which is unwanted ofcourse but we must not get NPE so we check for null and return 0
return rect.width = 0;
}
return rect.width = getCurrentImage().getWidth();
}
public double getHeight() {
if (getCurrentImage() == null) {
return rect.height = 0;
}
return rect.height = getCurrentImage().getHeight();
}
}
class Animator {
private ArrayList<BufferedImage> frames;
private ArrayList<Long> timings;
private int currIndex;
private long animationTime;
private long totalAnimationDuration;
private AtomicBoolean done;//used to keep track if a single set of frames/ an animtion has finished its loop
public Animator(ArrayList<BufferedImage> frames, ArrayList< Long> timings) {
currIndex = 0;
animationTime = 0;
totalAnimationDuration = 0;
done = new AtomicBoolean(false);
this.frames = new ArrayList<>();
this.timings = new ArrayList<>();
setFrames(frames, timings);
}
public boolean isDone() {
return done.get();
}
public void reset() {
totalAnimationDuration = 0;
done.getAndSet(false);
}
public void update(long elapsedTime) {
if (frames.size() > 1) {
animationTime += elapsedTime;
if (animationTime >= totalAnimationDuration) {
animationTime = animationTime % totalAnimationDuration;
currIndex = 0;
done.getAndSet(true);
}
while (animationTime > timings.get(currIndex)) {
currIndex++;
}
}
}
public BufferedImage getCurrentImage() {
if (frames.isEmpty()) {
return null;
} else {
try {
return frames.get(currIndex);
} catch (Exception ex) {//images might have been altered so we reset the index and return first image of the new frames/animation
currIndex = 0;
return frames.get(currIndex);
}
}
}
public void setFrames(ArrayList<BufferedImage> frames, ArrayList< Long> timings) {
if (frames == null || timings == null) {//so that constructor super(null,null) cause this to throw NullPointerException
return;
}
this.frames = frames;
this.timings.clear();
for (long animTime : timings) {
totalAnimationDuration += animTime;
this.timings.add(totalAnimationDuration);
}
}
}
class GamePanel extends JPanel {
private final static RenderingHints textRenderHints = new RenderingHints(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
private final static RenderingHints imageRenderHints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
private final static RenderingHints colorRenderHints = new RenderingHints(RenderingHints.KEY_COLOR_RENDERING, RenderingHints.VALUE_COLOR_RENDER_QUALITY);
private final static RenderingHints interpolationRenderHints = new RenderingHints(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
private final static RenderingHints renderHints = new RenderingHints(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
public static AtomicBoolean running = new AtomicBoolean(false), paused = new AtomicBoolean(false);
private int width, height, frameCount = 0, fps = 0;
private ArrayList<Entity> entities = new ArrayList<>();
private final Random random = new Random();
public GamePanel(int w, int h) {
super(true);//make sure double buffering is enabled
setIgnoreRepaint(true);//mustnt repaint itself the gameloop will do that
width = w;
height = h;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(width, height);
}
public void addEntity(Entity e) {
entities.add(e);
}
void clearEntities() {
entities.clear();
}
//Only run this in another Thread!
public void gameLoop() {
//This value would probably be stored elsewhere.
final double GAME_HERTZ = 30.0;
//Calculate how many ns each frame should take for our target game hertz.
final double TIME_BETWEEN_UPDATES = 1000000000 / GAME_HERTZ;
//At the very most we will update the game this many times before a new render.
//If you're worried about visual hitches more than perfect timing, set this to 1.
final int MAX_UPDATES_BEFORE_RENDER = 5;
//We will need the last update time.
double lastUpdateTime = System.nanoTime();
//Store the last time we rendered.
double lastRenderTime = System.nanoTime();
//If we are able to get as high as this FPS, don't render again.
final double TARGET_FPS = 60;
final double TARGET_TIME_BETWEEN_RENDERS = 1000000000 / TARGET_FPS;
//Simple way of finding FPS.
int lastSecondTime = (int) (lastUpdateTime / 1000000000);
//store the time we started this will be used for updating map and charcter animations
long currTime = System.currentTimeMillis();
while (running.get()) {
if (!paused.get()) {
double now = System.nanoTime();
long elapsedTime = System.currentTimeMillis() - currTime;
currTime += elapsedTime;
int updateCount = 0;
//Do as many game updates as we need to, potentially playing catchup.
while (now - lastUpdateTime > TIME_BETWEEN_UPDATES && updateCount < MAX_UPDATES_BEFORE_RENDER) {
updateGame(elapsedTime);//Update the entity movements and collision checks etc (all has to do with updating the games status i.e call move() on Enitites)
lastUpdateTime += TIME_BETWEEN_UPDATES;
updateCount++;
}
//If for some reason an update takes forever, we don't want to do an insane number of catchups.
//If you were doing some sort of game that needed to keep EXACT time, you would get rid of this.
if (now - lastUpdateTime > TIME_BETWEEN_UPDATES) {
lastUpdateTime = now - TIME_BETWEEN_UPDATES;
}
drawGame();//draw the game by invokeing repaint (which will call paintComponent) on this JPanel
lastRenderTime = now;
//Update the frames we got.
int thisSecond = (int) (lastUpdateTime / 1000000000);
if (thisSecond > lastSecondTime) {
//System.out.println("NEW SECOND " + thisSecond + " " + frameCount);
fps = frameCount;
frameCount = 0;
lastSecondTime = thisSecond;
}
//Yield until it has been at least the target time between renders. This saves the CPU from hogging.
while (now - lastRenderTime < TARGET_TIME_BETWEEN_RENDERS && now - lastUpdateTime < TIME_BETWEEN_UPDATES) {
//allow the threading system to play threads that are waiting to run.
Thread.yield();
//This stops the app from consuming all your CPU. It makes this slightly less accurate, but is worth it.
//You can remove this line and it will still work (better), your CPU just climbs on certain OSes.
//FYI on some OS's this can cause pretty bad stuttering. Scroll down and have a look at different peoples' solutions to this.
//On my OS(Windows 7 x64 intel i3) it does not allow for time to make enityt etc move thus all stands still
try {
Thread.sleep(1);
} catch (Exception e) {
}
now = System.nanoTime();
}
}
}
fps = 0;//no more running set fps to 0
}
private void drawGame() {
//Both revalidate and repaint are thread-safe — you need not invoke them from the event-dispatching thread. http://docs.oracle.com/javase/tutorial/uiswing/layout/howLayoutWorks.html
repaint();
}
private void updateGame(long elapsedTime) {
updateEntityMovements(elapsedTime);
checkForCollisions();
}
private void checkForCollisions() {
if (entities.get(0).intersects(entities.get(2)) || entities.get(1).intersects(entities.get(2))) {
if (entities.get(2).LEFT) {
entities.get(2).RIGHT = true;
entities.get(2).LEFT = false;
} else {
entities.get(2).LEFT = true;
entities.get(2).RIGHT = false;
}
System.out.println("Intersecting");
} /*
//This is best used when images have transparent and non-transparent pixels (only detects pixel collisions of non-transparent pixels)
if (entities.get(0).checkPerPixelCollision(entities.get(2)) | entities.get(1).checkPerPixelCollision(entities.get(2))) {
if (entities.get(2).LEFT) {
entities.get(2).RIGHT = true;
entities.get(2).LEFT = false;
} else {
entities.get(2).LEFT = true;
entities.get(2).RIGHT = false;
}
System.out.println("Intersecting");
}
*/
}
private void updateEntityMovements(long elapsedTime) {
for (Entity e : entities) {
e.update(elapsedTime);
e.move();
}
}
#Override
protected void paintComponent(Graphics grphcs) {
super.paintComponent(grphcs);
Graphics2D g2d = (Graphics2D) grphcs;
applyRenderHints(g2d);
drawBackground(g2d);
drawEntitiesToScreen(g2d);
drawFpsCounter(g2d);
frameCount++;
}
public static void applyRenderHints(Graphics2D g2d) {
g2d.setRenderingHints(textRenderHints);
g2d.setRenderingHints(imageRenderHints);
g2d.setRenderingHints(colorRenderHints);
g2d.setRenderingHints(interpolationRenderHints);
g2d.setRenderingHints(renderHints);
}
private void drawEntitiesToScreen(Graphics2D g2d) {
for (Entity e : entities) {
if (e.isVisible()) {
g2d.drawImage(e.getCurrentImage(), (int) e.getX(), (int) e.getY(), null);
}
}
}
private void drawFpsCounter(Graphics2D g2d) {
g2d.setColor(Color.WHITE);
g2d.drawString("FPS: " + fps, 5, 10);
}
private void drawBackground(Graphics2D g2d) {
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, getWidth(), getHeight());
//thanks to trashgod for lovely testing background :) http://stackoverflow.com/questions/3256269/jtextfields-on-top-of-active-drawing-on-jpanel-threading-problems/3256941#3256941
g2d.setColor(Color.BLACK);
for (int i = 0; i < 128; i++) {
g2d.setColor(new Color(random.nextInt(256), random.nextInt(256), random.nextInt(256)));//random color
g2d.drawLine(getWidth() / 2, getHeight() / 2, random.nextInt(getWidth()), random.nextInt(getHeight()));
}
}
}
This may be a legitimate use case for the low-level access afforded to a KeyListener. See How to Write a Key Listener and KeyEventDemo; don't forget to requestFocusInWindow(). Also consider offering keyboard control for one player and mouse control for the other.
Addendum: #David Kroukamp has adduced an appealing counter-example using key bindings. For reference, the example cited here illustrates one approach to managing key preferences.

Change background and font color of jpanel on button click

I want to change Background color of Jpanel and its font on button click.
Can anyone tell me what i am doing wrong?
Can I set JPanel background transparent?if yes How?
On button click test4.action method is called where i need to change Jpanel background color?
Here is the code:
import java.applet.*;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.event.*;
import javax.swing.*;
public class test3 extends Applet {
JPanel c;
JScrollPane s;
Button connect;
Panel controls;
Color back, fore;
public void init() {
back = Color.black;
fore = Color.white;
setBackground(Color.darkGray);
setLayout(new BorderLayout());
s = new JScrollPane(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
//s.setSize(100, 100);
add("Center", s);
c = new myCanvas11(this);
s.setOpaque(false);
s.setViewportView(c);
//s.add(c);
c.setSize(1000, 16000);
add("North", controls = new Panel());
controls.setLayout(new FlowLayout());
controls.add(connect = new Button("Change Color"));
}
public void start() {
// s.setScrollPosition(100, 100);
}
public boolean action(Event e, Object arg) {
back = Color.magenta;
fore = Color.blue;
//setBackground(back);
//invalidate();
//repaint();
c.setBackground(back);
c.repaint();
//s.getViewport().setBackground(back);
// s.getViewport().repaint();
//c.repaint();
c.setFocusable(true);
return true;
}
}
class myCanvas11 extends JPanel implements KeyListener {
Image buffImage;
Graphics offscreen;
boolean initDone = false;
int chw, chh; // size of a char (in pixels)
int chd; // offset of char from baseline
int width, height; // size of applet (in pixels)
int w, h; // size of applet (in chars)
Font fn;
Graphics gr;
int nh, nw;
test3 owner;
static int counter = 0;
myCanvas11(test3 t) {
super();
owner = t;
nh = 16000;
nw = 1000;
this.setOpaque(true);
this.setFocusable(true);
addKeyListener(this);
}
public void reshape(int nx, int ny, int nw1, int nh1) {
if (nw1 != width || nh1 != height) {
width = nw;
height = nh;
gr = getGraphics();
fn = new Font("Courier", Font.PLAIN, 11);
if (fn != null) {
gr.setFont(fn);
}
FontMetrics fnm = gr.getFontMetrics();
chw = fnm.getMaxAdvance();
chh = fnm.getHeight();
chd = fnm.getDescent();
// kludge for Windows NT and others which have too big widths
if (chw + 1 >= chh) {
chw = (chw + 1) / 2;
}
// work out size of drawing area
h = nh / chh;
w = nw / chw;
buffImage = this.createImage(nw, nh);
offscreen = buffImage.getGraphics();
//offscreen.setColor(Color.black);
//offscreen.fillRect(0, 0, nw, nh);
offscreen.setColor(Color.blue);
offscreen.setFont(fn);
if (initDone) {
offscreen.drawString("Hello World!", 0, 50);
} else {
offscreen.drawString("khushbu", 2, 50);
}
initDone = true;
offscreen.drawImage(buffImage, 0, 0, this);
}
super.reshape(nx, ny, nw, nh);
}
public void paint(Graphics g) {
// if (!initDone)
// initpaint(g);
// else
g.drawImage(buffImage, 0, 0, this);
//g.drawImage(buffImage, 0, 0, owner.back, this);
}
public void update(Graphics g) {
g.drawImage(buffImage, 0, 0, this);
super.update(g);
//g.drawImage(buffImage, 0, 0, owner.back, this);
}
public void initpaint(Graphics g) {
try {
nh = getHeight();
nw = getWidth();
gr = getGraphics();
fn = new Font("Courier", Font.PLAIN, 11);
if (fn != null) {
gr.setFont(fn);
}
FontMetrics fnm = gr.getFontMetrics();
chw = fnm.getMaxAdvance();
chh = fnm.getHeight();
chd = fnm.getDescent();
// kludge for Windows NT and others which have too big widths
if (chw + 1 >= chh) {
chw = (chw + 1) / 2;
}
// work out size of drawing area
h = nh / chh;
w = nw / chw;
buffImage = this.createImage(nw, nh);
offscreen = buffImage.getGraphics();
//offscreen.setColor(Color.black);
//offscreen.fillRect(0, 0, nw, nh);
offscreen.setColor(Color.white);
offscreen.setFont(fn);
if (initDone) {
offscreen.drawString("Hello World!", 0, 50);
} else {
offscreen.drawString("khushbu", 2, 50);
}
initDone = true;
g.drawImage(buffImage, 0, 0, this);
} catch (Exception e) {
e.printStackTrace();
}
}
/** Handle the key typed event from the text field. */
public void keyTyped(KeyEvent e) {
}
/** Handle the key pressed event from the text field. */
public void keyPressed(KeyEvent e) {
String s;
offscreen.setColor(owner.fore);
offscreen.setFont(fn);
for (int i = counter; i < counter + 25; ++i) {
s = Integer.toString(i);
offscreen.drawString(s, 3, i * chh);
offscreen.drawLine(10, i * chh, 160, i * chh);
}
//owner.s.setScrollPosition(0, counter * 16);
counter = counter + 25;
repaint();
}
/** Handle the key released event from the text field. */
public void keyReleased(KeyEvent e) {
}
public boolean keyDown(Event e, int k) {
String s;
offscreen.setColor(owner.fore);
offscreen.setFont(fn);
for (int i = counter; i < counter + 25; ++i) {
s = Integer.toString(i);
offscreen.drawString(s, 3, i * chh);
offscreen.drawLine(10, i * chh, 160, i * chh);
}
//owner.s.setScrollPosition(0, counter * 16);
counter = counter + 25;
repaint();
return true;
}
}
Can I set JPanel background transparent?if yes How?
Yes, just call setOpaque(false); on that JPanel. On the contrary, if you want the background to be painted, call setOpaque(true); on that panel.
Additional remarks:
public boolean action(Event e, Object arg) is Deprecated. Add the appropriate listeners instead.
Follow Java coding conventions and use an Uppercase letter for the first character of a class
You should almost never call getGraphics(); on a Component
Don't override public void paint(Graphics g) but protected void paintComponent(Graphics g). Don't forget to call super.paintComponent if you want the background to be painted
Why do you override reshape?
Why do you create an internal buffer? Swing already has built-in double buffering and they do a much better job.
If you overrode already paint() why do you also override update()? You are performing the same job twice.
Remove that gr class variable, it does not make any sense. When you use Graphics object, use the one provided as a parameter of the paintComponent method but don't keep a reference to it, as it will be disposed later.
counter does not need to be static. Try to avoid static as much as possible.
Try to avoid so much coupling between your classes. It makes your code harder to maintain and less predictable.
Next time, post an SSCCE with something else than an Applet (a JFrame, for example) unless the problem you are having really requires an Applet to be reproduced.

Categories