Starting animation with a button Java Swing - java

I'd like to create an animation of a few objects ("Spaceships") on the screen, with a start button. This is what I have so far:
public class SpaceGame extends JPanel implements ActionListener {
//The list of spaceships that should be painted
LinkedList<Spaceship> playingList = new LinkedList<Spaceship>();
Timer t = new Timer(5, this);
#Override
public void actionPerformed(ActionEvent e) {
ListIterator<Spaceship> iter = playingList.listIterator();
while (iter.hasNext()) {
Spaceship s = iter.next();
s.moveSpaceship();
}
repaint();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
for (Spaceship s : playingList)
s.drawSpaceship(g);
t.start();
}
public static void main(String[] args) {
SpaceGame game = new SpaceGame();
JFrame fr = new JFrame();
fr.setTitle("SPACE GAME");
fr.setSize(990,690);
fr.add(game);
game.playingList .add(new Spaceship(3, 0, 570));
game.playingList .add(new Spaceship(1, 250, 570));
game.playingList .add(new Spaceship(2, 500, 570));
JButton start = new JButton("START");
start.addActionListener(game);
fr.add(start,BorderLayout.SOUTH);
fr.setVisible(true);
fr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
where:
import java.awt.Graphics;
public class Spaceship {
int initialSpeed;
int locX, locY; //current location
public Spaceship(int initalSpeed, int initX, int initY) {
this.initialSpeed = initalSpeed;
locX = initX;
locY = initY;
}
public void drawSpaceship(Graphics g) {
g.setColor(Color.GREEN);
g.fillOval(locX, locY, 100, 40);
}
public void moveSpaceship() {
locY -= initialSpeed;
}
}
Of cousre the idea is that pressing the button would trigger the animation. The problem is that the animation would start automaticaly without the start button being pressed, and then when it ends, the button has no effect. How can I fix this?

Let's start with the glaringly obvious...
public void paintComponent(Graphics g) {
super.paintComponent(g);
for (Spaceship s : playingList)
s.drawSpaceship(g);
t.start();
}
Call t.start inside paintComponent is a very, very, very bad idea. You do not control the paint process (ie when paintComponent gets called), so it might be called at any time for any number of reasons, often in quick succession.
You should have a look at Painting in AWT and Swing for more details about how painting works in Swing
Painting should simply paint the current state of the component and nothing else.
The second problem is the fact that both the Timer and the button are calling the same actionPerformed method, which doesn't really make sense. In fact, in a perfect world, you wouldn't implement ActionListener directly like this and instead make use of Anonymous Classes which would guard against outside classes calling the method directly or indirectly.
So, what's the solution? Add a method to your SpaceGame which can be called to start the animation, something like...
public class SpaceGame extends JPanel implements ActionListener {
//The list of spaceships that should be painted
LinkedList<Spaceship> playingList = new LinkedList<Spaceship>();
Timer t = new Timer(5, this);
public void start() {
if (t.isRunning()) {
return;
}
t.start();
}
#Override
public void actionPerformed(ActionEvent e) {
ListIterator<Spaceship> iter = playingList.listIterator();
while (iter.hasNext()) {
Spaceship s = iter.next();
s.moveSpaceship();
}
repaint();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
for (Spaceship s : playingList) {
s.drawSpaceship(g);
}
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
SpaceGame game = new SpaceGame();
JFrame fr = new JFrame();
fr.setTitle("SPACE GAME");
// This is unadvisable :/
fr.setSize(990, 690);
fr.add(game);
game.playingList.add(new Spaceship(3, 0, 570));
game.playingList.add(new Spaceship(1, 250, 570));
game.playingList.add(new Spaceship(2, 500, 570));
JButton start = new JButton("START");
start.addActionListener(new ActionListener() {
#Override
public void actionPerformed(java.awt.event.ActionEvent e) {
game.start();
}
});
fr.add(start, BorderLayout.SOUTH);
fr.setVisible(true);
fr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
});
}
}
Then update your main method to call it...
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
SpaceGame game = new SpaceGame();
JFrame fr = new JFrame();
fr.setTitle("SPACE GAME");
// This is unadvisable :/
fr.setSize(990, 690);
fr.add(game);
game.playingList.add(new Spaceship(3, 0, 570));
game.playingList.add(new Spaceship(1, 250, 570));
game.playingList.add(new Spaceship(2, 500, 570));
JButton start = new JButton("START");
start.addActionListener(new ActionListener() {
#Override
public void actionPerformed(java.awt.event.ActionEvent e) {
game.start();
}
});
fr.add(start, BorderLayout.SOUTH);
fr.setVisible(true);
fr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
});
}

Related

Adding animations to JPanel

I've written some code that essentially animates a sequence of images and adds them to a frame when I run the file.
I want to implement a functionality where I can add this animation to two different areas of a JPanel that has a BorderLayout (North and West).
I want to do this using a button but I don't know how to do that. I am new to event handling and layout managers.
How do I go about this?
My code for the animation:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class ImageSequence extends JPanel implements ActionListener {
private ImageSQPanel imageSQPanel;
private static int frameNumber = -1;
private Timer timer;
private void buildUI(Container container, Image[] arrows) {
int fps = 10;
int delay = 1000 / fps;
timer = new Timer(delay, this);
timer.setInitialDelay(0);
timer.setCoalesce(true);
imageSQPanel = new ImageSQPanel(arrows);
container.add(imageSQPanel, BorderLayout.CENTER);
}
private synchronized void startAnimation() {
if (!timer.isRunning()) {
timer.start();
}
}
public void actionPerformed(ActionEvent e) {
frameNumber++;
imageSQPanel.repaint();
}
class ImageSQPanel extends JPanel {
Image arrowAnimation[];
ImageSQPanel(Image[] arrowAnimation) {
this.arrowAnimation = arrowAnimation;
}
//Draw the current frame of animation.
public void paintComponent(Graphics g) {
super.paintComponent(g); //paint background
//Paint the frame into the image.
try {
g.drawImage(arrowAnimation[ImageSequence.frameNumber % 10], 0, 0, this);
} catch (ArrayIndexOutOfBoundsException e) {
//On rare occasions, this method can be called
//when frameNumber is still -1. Do nothing.
}
}
}
//Invoked only when this is run as an application.
public static void main(String[] args) {
Image[] waving = new Image[7];
for (int i = 1; i <= 7; i++) {
waving[i - 1] = Toolkit.getDefaultToolkit().getImage(
"/Users/sarthaksachdeva/Documents/IntelliJ Projects/Animation/src/images/Arrow" + i + ".png");
}
JFrame f = new JFrame("ImageSequenceTimer");
f.addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
ImageSequence controller = new ImageSequence();
controller.buildUI(f.getContentPane(), waving);
controller.startAnimation();
f.setSize(new Dimension(200, 200));
f.setVisible(true);
}
}

Using swing timer and ImageIcons

Im trying to make a function that receive a image and imageicon as parameter, darken it for 2 secs and turn it back to normal, but i cant make the timer works as planned.
public void blinkImage(Image e, ImageIcon f) {
ActionListener listener = new ActionListener(){
public void actionPerformed(ActionEvent event){
Graphics2D g2 = (Graphics2D) e.getGraphics();
g2.setColor(new Color(0, 0, 0, 50));
g2.fillRect(0, 0, f.getIconWidth(), f.getIconHeight());
}
};
Timer displayTimer = new Timer(2000, listener);
displayTimer.start();
displayTimer.stop();
}
OBS: after this call will be a setIcon(f) in the main window, turning it back to normal. My question is: where should I put the start() and stop() calls? There is a better way to do it?
Thanks and sorry for bad english.
public void blinkImage(Image e, ImageIcon f)
Not sure why you have two parameters. Is the Image the Image in the Icon?
Painting onto the Image will be permanent. So if it is the same Image as the Icon you can't restore the Icon to its original state.
displayTimer.start();
displayTimer.stop();
You can't invoke stop() right after you start() the Timer because the Timer will never fire. So all you need is the start().
Since you only want the Timer to fire once you would just use:
timer.setRepeats( false );
then you don't have to worry about stopping the Timer.
One approach is create a custom Icon with two states. Then you can toggle the state of the Icon:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class DarkIcon implements Icon
{
private Icon icon;
private Color color;
private boolean dark = false;
public DarkIcon(Icon icon, Color color)
{
this.icon = icon;
this.color = color;
}
public void setDark(boolean dark)
{
this.dark = dark;
}
#Override
public int getIconWidth()
{
return icon.getIconWidth();
}
#Override
public int getIconHeight()
{
return icon.getIconHeight();
}
#Override
public void paintIcon(Component c, Graphics g, int x, int y)
{
icon.paintIcon(c, g, x, y);
if (dark)
{
g.setColor(color);
g.fillRect(x, y, getIconWidth(), getIconHeight());
}
}
public static void main(String[] args)
{
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGUI();
}
});
}
public static void createAndShowGUI()
{
Icon icon = new ImageIcon("mong.jpg");
DarkIcon darkIcon = new DarkIcon(icon, new Color(0, 0, 0, 128));
JLabel label = new JLabel( darkIcon );
Action blink = new AbstractAction()
{
#Override
public void actionPerformed(ActionEvent e2)
{
darkIcon.setDark( false );
label.repaint();
}
};
Timer timer = new Timer(2000, blink);
timer.setRepeats( false );
JButton button = new JButton("Blink Icon");
button.addActionListener( new ActionListener()
{
#Override
public void actionPerformed(ActionEvent e)
{
darkIcon.setDark(true);
label.repaint();
timer.restart();
}
});
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(label);
f.add(button, BorderLayout.SOUTH);
f.pack();
f.setLocationRelativeTo( null );
f.setVisible(true);
}
}

Fluent animation in Swing

I'd like to create a fluent 60fps animation. The animation itself seems to be approximately at 10fps, even though my render thread calls the run method 60 times a second. I'm pretty sure the problem is that the Swing was not created with animations in mind. So what is the easiest way to solve this? Thanks a lot
Code:
import aurelienribon.tweenengine.Tween;
import aurelienribon.tweenengine.TweenManager;
import object.Text;
import java.awt.*;
public class Main implements RenderThread.RunnerIface {
private final Frame mFrame;
private RenderThread mRenderThread;
private TweenManager mTweenManager;
private Text mText = new Text("text", 250, 250);
public Main() {
mFrame = new Frame();
mRenderThread = new RenderThread(this);
mTweenManager = new TweenManager();
Tween.registerAccessor(Text.class, new TextAccessor());
new Thread(mRenderThread).start();
this.animateRight();
}
private void animateLeft() {
Tween.to(mText, -1, 1000.0f)
.target(100, 250)
.start(mTweenManager);
}
private void animateRight() {
Tween.to(mText, -1, 10000.0f)
.target(400, 250)
.start(mTweenManager);
}
public static void main(String[] args) {
new Main();
}
#Override
public void run(float delta) {
mTweenManager.update(delta);
System.out.format("Delta: %f, FPS: %f\n", delta, 1000/delta);
Graphics g = mFrame.getCanvas().getGraphics();
g.setColor(Color.white);
g.fillRect(0, 0, mFrame.getCanvas().getWidth(), mFrame.getCanvas().getHeight());
mText.render(g);
}
}
Edit:
Second attempt:
public class DrawPanel extends JPanel implements ActionListener {
private Timer tm = new Timer(30, this);
JLabel label = new JLabel("Here is my label");
private int x = 0, velX = 2;
private long time;
public DrawPanel() {
super();
// this.setLayout(null);
this.x = this.getWidth();
this.label.setBounds(x,100,100,100);
this.add(label);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
/* g.setColor(Color.white);
g.fillRect(0, 0, this.getWidth(), this.getHeight());*/
// x=this.getWidth();
time = System.currentTimeMillis();
tm.start();
}
#Override
public void actionPerformed(ActionEvent e) {
// x+=1;
int l = (int) ((System.currentTimeMillis() - time)*0.1);
this.time = System.currentTimeMillis();
System.out.println(l);
x=x+l;
this.label.setBounds(x,100,100,100);
this.repaint();
}
}
public class Main {
private Frame mFrame;
private DrawPanel drawPanel;
public Main() {
mFrame = new Frame();
drawPanel = new DrawPanel();
mFrame.add(drawPanel);
}
public static void main(String[] args) {
new Main();
}
}
So I completely rewrote my code following MadProgrammer's advices. Now I use swing timer instead of my render thread. Result is still the same, the animation is dropping frames and it's unusable. I'm confident that the actionPerformed method is called as frequently as it should be. I guess the problem is somehow hidden in the repaint method. I tried to call the repaint method on the JFrame but it didn't help.
I was following this tutorial, but everythink seems to work fine in the creator's case.
Thanks
Due to the performance problems with Swing I decided to use JavaFX instead. Since I've created quite a lot of code in Swing I didn't want to toss it all away. Fortunately there is JFXPanel for that.
Here is the code with animation example:
public class AnimationPanel extends JFXPanel {
private Text[] nodes = new Text[1];
public AnimationPanel() {
super();
Text t = new Text(10,50, "This is test");
t.setFill(Color.RED);
nodes[0] = t;
final Scene scene = new Scene(new Group(nodes), 800, 600, Color.BLACK);
this.setScene(scene);
new AnimationTimer() {
public int x = 0;
#Override
public void handle(long now) {
for (int i=0; i<1; i++) {
final Node node = nodes[i];
node.setTranslateX(x);
x++;
}
}
}.start();
}
}
You can use this panel as a regular Swing panel.

Doing an animation in Java

I am doing a java assignment for next Saturday.
Its going really well, however I'm struggling with one section.
Here I want to reveal a set of numbers in a String, one at a time.
I tried slowing down the loop with 'Thread.sleep(1000);'
however nothing is displaying until the thread is finished
the following is a section of the graphics class where the problem is occuring
is there something I'm missing?
public void paint(Graphics g)
{
setSize(550, 300);
//this draws all the random numbers, revealing the ans to the user
if (revealNum == 0)
{
g.setColor(Color.BLUE);
g.drawString(randomNumber, 20, 20); //draw String ("the String", x, y)
}
//this reveals the numbers 1 by 1 to the user at the start of the game
if (revealNum==1)
{
for (int x = 0; x < limit; x++)
{
g.setColor(Color.BLUE);
g.drawString(""+x, 20, 20); //draw String ("the String", x, y)
try{
Thread.sleep(1000);
}catch(InterruptedException ex){
System.out.print("Error");
}
repaint();
}
//slow down the loop to show the user
}
Since yours is a GUI, calling Thread.sleep will put the entire app to sleep. Instead use a Swing Timer. Inside the Timer's ActionListener, add another letter to the displayed String, and then stop the Timer via the stop() method once the String is complete.
e.g.,
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import javax.swing.*;
public class SimpleAnimation extends JPanel {
public static final int TIMER_DELAY = 1000;
private JTextField textField = new JTextField(10);
private JLabel displayLabel = new JLabel("", SwingConstants.CENTER);
public SimpleAnimation() {
Action btnAction = new DoItBtnAction("Do It!", KeyEvent.VK_D);
JPanel topPanel = new JPanel();
topPanel.add(textField);
topPanel.add(new JButton(btnAction));
textField.addActionListener(btnAction);
setLayout(new GridLayout(2, 1));
add(topPanel);
add(displayLabel);
}
private class DoItBtnAction extends AbstractAction {
private String textFieldText = "";
public DoItBtnAction(String name, int mnemonic) {
super(name);
putValue(MNEMONIC_KEY, mnemonic);
}
#Override
public void actionPerformed(ActionEvent e) {
displayLabel.setText("");
setEnabled(false);
textFieldText = textField.getText();
new Timer(TIMER_DELAY, new ActionListener() {
private int i = 0;
#Override
public void actionPerformed(ActionEvent e) {
if (i >= textFieldText.length()) {
((Timer) e.getSource()).stop();
DoItBtnAction.this.setEnabled(true);
} else {
displayLabel.setText(displayLabel.getText() + textFieldText.charAt(i));
i++;
}
}
}).start();
}
}
private static void createAndShowGui() {
JFrame frame = new JFrame("SimpleAnimation");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new SimpleAnimation());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
Also,
If yours is a Swing GUI, it would be easier to display your text in a JLabel or a JTextField rather than trying to paint it on the GUI.
If this is Swing, don't override paint(Graphics g) but rather the paintComponent(Graphics g) method of a JPanel or JComponent.
You should use a javax.swing.Timer
Here is an example
JLabel l = ...;
Timer t = new Timer(1000, new ActionListener() {
public void actionPerformed(ActionEvent ae) {
if (l.getText().equals("1")) l.setText("2");
else if (l.getText().equals("2)) l.setText("1");
}
});

why doesn't this paint component work?

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
import javax.swing.event.*;
public class Main {
public static void main(String[]args){
#SuppressWarnings("unused")
Gui g = new Gui();
}
}
#SuppressWarnings("serial")
class Gui extends JFrame{
Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
double width = screen.getWidth();
double height = screen.getHeight();
JPanel canvas = new JPanel();
Point mloc = new Point();
JButton br = new JButton("Red");
JButton bb = new JButton("Blue");
JButton bg = new JButton("Green");
JButton wipe = new JButton("Wipe");
JLabel brushwidth = new JLabel("Width = ",JLabel.CENTER);
public JSlider s = new JSlider();
JButton image = new JButton("Image");
Point start = null;
Point current = null;
boolean entered = false;
Color c = Color.red;
public double bwidth = 3;
Gui(){
super("PaintPot");
setSize((int)width/4,(int)height/2);
setVisible(true);
setResizable(false);
setLayout(null);
setLocationRelativeTo(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
add(s);
s.setSize(getWidth()-20, 20);
s.setLocation(10, 450);
s.setBackground(Color.gray);
s.setForeground(Color.orange);
s.setMajorTickSpacing(50);
s.setMinorTickSpacing(1);
s.setValue((int)bwidth);
s.addChangeListener(new ChangeListener(){
#Override
public void stateChanged(ChangeEvent e) {
bwidth = s.getValue();
brushwidth.setText("Width = "+s.getValue());
}
});
brushwidth.setText("Width = "+s.getValue());
add(brushwidth);
brushwidth.setSize(70,30);
brushwidth.setLocation(90,410);
brushwidth.setBackground(Color.gray);
brushwidth.setForeground(Color.orange);
brushwidth.setOpaque(true);
brushwidth.setVisible(true);
add(wipe);
wipe.setSize(70,30);
wipe.setLocation(10, 410);
wipe.setBackground(Color.gray);
wipe.setForeground(Color.orange);
wipe.setVisible(true);
add(br);
br.setSize(60,30);
br.setLocation(10, 10);
br.setBackground(Color.red);
br.setForeground(Color.white);
br.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent arg0) {
c = Color.red;
}
});
br.setVisible(true);
add(bb);
bb.setSize(60,30);
bb.setLocation(80, 10);
bb.setBackground(Color.blue);
bb.setForeground(Color.white);
bb.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
c = Color.blue;
}
});
bb.setVisible(true);
add(bg);
bg.setSize(70,30);
bg.setLocation(150, 10);
bg.setBackground(Color.green);
bg.setForeground(Color.white);
bg.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent e){
c = Color.green;
}
});
bg.setVisible(true);
add(image);
image.setSize(70,30);
image.setLocation(230, 10);
image.setBackground(Color.gray);
image.setForeground(Color.orange);
wipe.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent e){
}
});
image.setVisible(true);
canvas(this,canvas);
}
public void canvas(JFrame f, JPanel p){
p.setSize(425,350);
p.setBorder(BorderFactory.createLineBorder(Color.black, 3));
p.setLocation(10, 50);
p.addMouseListener(new MouseListener(){
#Override
public void mouseClicked(MouseEvent arg0) {}
#Override
public void mouseEntered(MouseEvent arg0) {entered = true;}
#Override
public void mouseExited(MouseEvent arg0) {entered = false;}
#Override
public void mousePressed(MouseEvent arg0) {}
#Override
public void mouseReleased(MouseEvent arg0) {}
});
p.addMouseMotionListener(new MouseAdapter(){
public void mouseDragged(MouseEvent e){
mloc = e.getLocationOnScreen();
if(entered = true){
paintComponent(getGraphics());
}
}
});
f.add(p);
}
public void paintComponent(Graphics g){
g.drawOval(mloc.x, mloc.y, (int)bwidth, (int)bwidth);
}
}
I'm trying to get this app to paint inside the JPanel but I can't seem to get it to work,
I want it to draw a line whenever I move my mouse. It's just the public void paint bit I can't get to grips with nothing seem to work.
Thanks
JFrame does not have a paintComponent() method.
Custom painting is done by overriding the paintComponent() method of a JPanel (or JComponent) and then you add the panel to the JFrame.
Read the section from the Swing tutorial on Custom Painting for more information and examples. You will also need to override the getPreferredSize() method.
Also, don't use a null layout. Swing was designed to be used with layout managers.
This if(entered = true){ is an assigment operator not a conditional. Instead you want if(entered == true){
paintComponent is meant to be overriden and not called explicitly. Don't explicitly call paintComponent when what you mean to do is call repaint()
JFrame has no paintComponent method, so you aren't actually overriding any paint functionality. For JFrame you should override paint, though I'd advise against it, and paint with JPanel or JComponent
In a paintComponent or paint method you should also be calling super.paintComponent or super.paint, respectively, as to not break the paint chain.

Categories