Stop flickering in simple Java animation - java

I have a simple applet that animates a rectangle along the x-axis of the canvas. The problem is that it flickers. I have tried to Google this problem, but I didn't come up with anything useful or anything that I understood.
I am relatively new to Java.
Thanks!
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.*;
import javax.swing.*;
public class simpleAnimation extends JApplet implements ActionListener {
Timer tm = new Timer(10, this);
int x = 0, velX = 2;
public void actionPerformed(ActionEvent event) {
if (x < 0 || x > 550){
velX = -velX;
}
x = x + velX;
repaint();
}
public void paint ( Graphics g ) {
super.paint(g);
g.setColor(Color.RED);
g.fillRect(x, 30, 50, 30);
tm.start();
}
}
**********UPDATED CODE WITHOUT FLICKER**********
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import javax.swing.*;
public class simpleAnimation extends JApplet implements ActionListener
{
Graphics bufferGraphics;
Image offscreen;
Dimension dim;
int x = 3, velX = 2;
Timer tm = new Timer(10, this);
public void init()
{
dim = getSize();
offscreen = createImage(dim.width,dim.height);
bufferGraphics = offscreen.getGraphics();
}
public void paint(Graphics g)
{
bufferGraphics.clearRect(0,0,dim.width,dim.width);
bufferGraphics.setColor(Color.red);
bufferGraphics.fillRect(x,50,50,20);
g.drawImage(offscreen,0,0,this);
tm.start();
}
public void update(Graphics g)
{
paint(g);
}
public void actionPerformed(ActionEvent evt)
{
if ( x < 0 || x > 550){
velX = -velX;
}
x = x + velX;
repaint();
}
}
I used this applet as a template.

I always struggle with this concept of double buffering.
Here is my example that overrides paint() of the JApplet and a paintComponent() of a JPanel, which uses double buffering by default.
I don't see any difference in the apparent flickering.
//<applet code="SimpleAnimation.class" width="600" height="300"></applet>
import java.awt.*;
import java.awt.Graphics;
import java.awt.event.*;
import javax.swing.*;
public class SimpleAnimation extends JApplet implements ActionListener {
Timer tm = new Timer(10, this);
int x = 0, velX = 2;
JPanel panel;
public void init()
{
panel = new JPanel()
{
#Override
public Dimension getPreferredSize()
{
return new Dimension(50, 100);
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
g.setColor(Color.RED);
g.fillRect(x, 30, 50, 30);
}
};
add(panel, BorderLayout.SOUTH);
tm.start();
}
public void actionPerformed(ActionEvent event) {
if (x < 0 || x > 550){
velX = -velX;
}
x = x + velX;
repaint();
// panel.repaint();
}
public void paint ( Graphics g ) {
super.paint(g);
g.setColor(Color.RED);
g.fillRect(x, 30, 50, 30);
}
}
I test the code using: appletviewer SimpleAnimation.java
Is my concept of double buffering flawed, or my implementation, or both?

The problem is, top level containers like JApplet aren't double buffered. This means that when it's updated, the screen flickers as each action is done directly onto the screen.
Instead, you should create a custom component, using something like a JPanel, and override its paintComponent method and perform your custom painting actions there.
Because Swing components are double buffered the results of the paint action are buffered before they are painted to the screen, making it a single action
Take a look at Performing Custom Painting for more details

What you're doing now works like this:
public void paint ( Graphics g ) {
// draw the entire area white
super.paint(g);
g.setColor(Color.RED);
// draw a rectangle at the new position
g.fillRect(x, 30, 50, 30);
}
So with every step, you first wipe out your rectangle, and then draw it fresh. Thus the flickering - the pixels under the rectangle keep changing from white to red to white to red to white to red...
Now observe that the smallest amount of painting you need to do is (supposing rectangle moves to the right) this:
draw velx pixels on the left WHITE
draw velx pixes on the right RED
If you do that, your animation will be smooth.
Computing that can be quite challenging though, especially when your shape is more complicated than just a square / your movement is more complex. That's where double buffering comes in.
With double buffering, you create an in-memory image that is the same size as your screen. You paint your entire theme there. Then you paint that image on your screen all at once.
When doing that, there won't be an intermediate step of "entire screen is WHITE"; thus no flickering. But note that you end up re-painting the entire screen rather than just the area that changed, which isn't ideal. Consider using clipping - a technique where you repaint a pre-defined area of the image and not the entire thing.

Related

Stop JPanel's paintComponent method from painting the background

I'm making a snake game in Java.
To make it efficient, I only paint the positions that have changed this frame (the first and last cells of the snake).
Here's my code:
public class GameCanvas extends JPanel {
private final List<GameChange> changes;
public GameCanvas() {
setBackground(Color.darkGray);
changes = new ArrayList<>();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
for (GameChange change : changes) {
Vector2 pos = change.position;
int val = change.value;
if (val != 0) g2d.setColor(Color.BLACK);
else g2d.setColor(Color.WHITE); //getBackground();
g2d.fillRect(GameWindow.pixelSize * pos.x,GameWindow.pixelSize * pos.y, GameWindow.pixelSize, GameWindow.pixelSize);
}
changes.clear();
}
public void applyChanges(List<GameChange> changes) {
this.changes.addAll(changes);
repaint();
}
}
The only problem is that the paintComponent method is repainting the background and the middle of the snake is disappearing.
The black cell is the new head and the white one is the one that I'm deleting. The middle cell was supposed to be black.
EDIT:
Everyone is telling me to draw the whole map but I really want to work on performance. This is what I've done so far:
private void paintChanges(List<GameChange> changes) {
Graphics2D g2d = (Graphics2D) getGraphics();
// Here I paint only the changes
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
// Here I paint everything
}
When the snake moves I only paint the changes but if the the frame gets repainted automatically the snake wont disappear. It appears to be working but I'm not sure if it's safe to use the getGraphics() method.
This is obviously not your game but it may help dispel the notion that you need to do something special to increase painting efficiency.
To create the snake, hold the left button down and drag out a curved line.
then release the button and just move the mouse around in the panel and watch the "snake" follow the mouse.
you can add more points by dragging the mouse at any time.
This works by simply creating a list of points and drawing a line in between them. As the mouse moves it removes the first point and adds the current one to the end. The paint method just iterates thru the list for each mouse movement and draws all the lines, giving the appearance of movement.
There is one obvious (and perhaps other, not so obvious) flaws with this. The number of points is constant but the snake expands and contracts in size since the points are spread out as the mouse moves faster so the lines between the points are longer. But that is not related to painting but to the speed of the mouse movement.
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class SimpleSnake extends JPanel {
JFrame frame = new JFrame("Simple Snake");
List<Point> snake = new ArrayList<>();
final static int WIDTH = 500;
final static int HEIGHT = 500;
public static void main(String[] args) {
SwingUtilities.invokeLater(()-> new SimpleSnake());
}
public SimpleSnake() {
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
addMouseMotionListener(new MyMouseListener());
setBackground(Color.white);
frame.add(this);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(WIDTH, HEIGHT);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
if (snake.size() < 2) {
return;
}
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.black);
g2d.setStroke(new BasicStroke(3)); // line thickness
Point start = snake.get(0);
for(int i = 1; i < snake.size(); i++) {
Point next = snake.get(i);
g2d.drawLine(start.x, start.y, next.x, next.y);
start = next;
}
g2d.dispose();
}
public class MyMouseListener extends MouseAdapter {
public void mouseDragged(MouseEvent me) {
snake.add(new Point(me.getX(), me.getY()));
repaint();
}
public void mouseMoved(MouseEvent me) {
if(snake.isEmpty()) {
return;
}
snake.remove(0);
snake.add(new Point(me.getX(), me.getY()));
repaint();
}
}
}

Rotating a graphic using arc2d

i am trying to do a roulette casino game, so for this i made my roulette using the Arc2D package.
My code below
package roulette;
import javax.swing.*;
import java.awt.*;
import java.awt.geom.Arc2D;
import java.awt.geom.AffineTransform;
import javax.swing.Timer;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class RouletteInterface extends JPanel{
public int spinValue = 0;
public void paint(Graphics g){
Graphics2D g2d = (Graphics2D)g;
paintRoulette(g2d);
}
public void paintRoulette(Graphics2D g2d) {
RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHints(hints);
AffineTransform at = AffineTransform.getTranslateInstance(10, 10);
at.rotate(spinValue, 10, 10);
double angle = 360 / 36.9;
double startAngle = 0;
int color = 0;
for(int i = 0; i < 37; i++) {
if(i == 0) {
g2d.setColor(Color.GREEN);
} else {
if(color == 0) {
g2d.setColor(Color.BLACK);
color = 1;
} else {
g2d.setColor(Color.RED);
color = 0;
}
}
g2d.fill(new Arc2D.Double(100, 100, 300, 300, startAngle, angle, Arc2D.PIE));
startAngle += angle;
}
g2d.transform(at);
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
spinValue += 0.01;
repaint();
}
});
timer.start();
}
}
In short i am not using a generalpath because i want to fill each arc with color red/green or black like the original roulette, and for the rotation i tried using a timer to increase the spinValue (this worked for me but when i use a generalpath) for the AfinneTransformation, but when i run the code, well, nothing happens. It shows me only the roulette without animation. What can i do?
Thanks in advance.
Painting and graphics in general are quite advanced topics, Java/Swing does a good job to "commonalise" the APIs into something which is reasonable easy to use, but still takes time and effort to learn and understand fully.
I would highly recommend having Performing Custom Painting, Painting in AWT and Swing and 2D Graphics and the JavaDocs booked marked, as you will be coming back to them on a regular bases (I still do)
There are lots of issues, which are compounding to make your life difficult.
Starting with...
public void paint(Graphics g){
Graphics2D g2d = (Graphics2D)g;
paintRoulette(g2d);
}
You should favour overriding paintComponent instead of paint, paint is a complicated process and you need to choose your entry point into carefully. Also, you should always call the paint methods super method, unless you are absolutely, positively prepared to take over its core functionality yourself.
In your case, you should also be making a copy of the Graphics context before passing it to paintRoulette, as Graphics is a shared resource and the transformations you are applying will cause issues for anything which is painted after your component.
Transformations...
AffineTransform at = AffineTransform.getTranslateInstance(10, 10);
at.rotate(spinValue, 10, 10);
This is somewhat interesting. You're creating translation of 10x10 which will move the origin point of the Graphics context. You then apply a rotation, which is anchored to 10x10.
The reason I mention it is because you then do...
g2d.fill(new Arc2D.Double(100, 100, 300, 300, startAngle, angle, Arc2D.PIE));
This means that the arc is offset by 110x110 from the corner of the component (add in your translation) and you'll be rotating about a point 20x20 from the component's top/left corner (add in your translation) ... this is weird to me because the centre of the of wheel is actually at 250x250 (from the component's top/left corner) which is going to make for one very weird affect.
Finally, you apply the transformation AFTER the painting is done AND then create a Timer inside the paint method...
Painting is done in serial. So one operation will effect the next, this will mean you will need to apply the transformation BEFORE you paint something (that you want transformed)
You also need to understand that you don't control the paint process, this means that your component may be painted for any number of reason at any time without your interaction. This means you could an infinite number of Timers, over a very small period of time.
Instead, your timer should be controlled externally from the paint process.
One other thing that took me some time to work out is...
public int spinValue = 0;
//...
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
spinValue += 0.01;
repaint();
}
});
You declare spinValue as int, but are adding a floating point value to it, this will have the effect of the decimal component been truncated, so the value will ALWAYS be 0.
Also, AffineTransform#rotate expects angles to be in radians, not degrees. Not sure if it's important, but you should be aware of it.
Runnable example...
Okay, so after applying the above, the code "might" look something like...
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.geom.AffineTransform;
import java.awt.geom.Arc2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new RoulettePane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class RoulettePane extends JPanel {
private double spinValue = 0;
private Timer timer;
public RoulettePane() {
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
spin();
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(300, 300);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
paintRoulette(g2d);
g2d.dispose();
}
protected void spin() {
if (timer != null && timer.isRunning()) {
return;
}
timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
spinValue += 0.01;
repaint();
}
});
timer.start();
}
protected void paintRoulette(Graphics2D g2d) {
RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setRenderingHints(hints);
int width = getWidth();
int height = getHeight();
int dimeter = Math.min(width, height);
AffineTransform at = AffineTransform.getRotateInstance(spinValue, dimeter / 2, dimeter / 2);
g2d.transform(at);
double angle = 360 / 36.9;
double startAngle = 0;
int color = 0;
for (int i = 0; i < 37; i++) {
if (i == 0) {
g2d.setColor(Color.GREEN);
} else {
if (color == 0) {
g2d.setColor(Color.BLACK);
color = 1;
} else {
g2d.setColor(Color.RED);
color = 0;
}
}
g2d.fill(new Arc2D.Double(0, 0, dimeter, dimeter, startAngle, angle, Arc2D.PIE));
startAngle += angle;
}
}
}
}
nb: I took the translation out for the time been as I wanted to focus on making the output more dynamic based on the actual width/height of the component

repaint() suddenly doesn't work

I've made about 30 different google searches and didn't come up with an answer, so I came here. So I'm trying to move the player's rectangle (the black square) left and right across the screen. It worked fine when I used regular graphics, but now that I'm using Graphics2D the repaint() seems to do nothing (i.e. when you press the left and right arrow keys, the rectangle doesn't move).
import java.util.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JComponent;
import javax.swing.JFrame;
public class boxface extends JComponent implements KeyListener {
private boxobj obj;
private int x=0, y=650;
public void keyPressed(KeyEvent e) {
if(e.getKeyCode()== KeyEvent.VK_RIGHT)
moveRight();
else if(e.getKeyCode()== KeyEvent.VK_LEFT)
moveLeft(); }
public void keyReleased(KeyEvent e) {}
public void keyTyped(KeyEvent e) {}
Rectangle player = new Rectangle(x, y, 50, 50);
Rectangle floor = new Rectangle(0, 700, 750, 700);
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g.setColor(Color.GREEN);
g2.fill(floor);
g.setColor(Color.BLACK);
g2.fill(player); }
public void moveLeft() {
if(x > 0) {
x -= 50;
repaint(); }}
public void moveRight() {
if(x < 700) {
x += 50;
repaint(); }}
public boxface(){
this.obj=new boxobj();
addKeyListener(this);
setFocusable(true);
setFocusTraversalKeysEnabled(false); }
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setBounds(400, 200, 756, 779);
f.setMinimumSize(new Dimension(756, 0));
f.setResizable(false);
f.getContentPane().add(new boxface());
f.setVisible(true);
}
});
final java.util.Timer tmr = new java.util.Timer();
tmr.scheduleAtFixedRate(new TimerTask()
{
public void run()
{
System.out.println("A second has passed.");
/* the idea is that I could make a square with random
* dimensions (within a certain limit), so that every
* time the timer loops, a new, random square is made.
* I just can't seem to move the rectangles using
* repaint(); because they're Graphics2D rectangles,
* and I can't find a way around this.
*
* An example of this can be shown if you run this
* code; the "player" rectangle cannot be moved, even
* though keylistener is picking up inputs and the
* rectangle's co-ordinates are being changed. In
* other words, repaint(); isn't doing anything. */
}
},0,1000);
}//end main
}//end class
Also, the "boxobj" class is just an empty class right now. It's where I plan to put the initialization of the random rectangles. I'll just put it in here for easy copy-paste.
public class boxobj {
}
The problem is that you're updating the x variable, but painting the player object.
When you construct player (via Rectangle player = new Rectangle(x, y, 50, 50);) it takes a copy of the value of x at the time that line is executed. Since you're declaring and initializing at the same time, we know that x is zero so player will be instantiated with (0, 650, 50, 50).
Later on, the user hits the right arrow key and your event listener fires. This increases x to 50 and calls repaint but, importantly, does not update the player object at all. When the paintComponent method gets called by the painting system player is still (0, 650, 50, 50).
Essentially x and y record the player's position, but you're using the player object to draw the player and those variables are not getting updated in tandem.
The best way to correct this is to store the player's position in exactly one place. You can either keep x and y and modify your paintComponent method to use those, or you could throw away those two variables and modify the player object instead (with player.setLocation). Either way will work.

Transparent Panels

I am making a game where a user has to draw lines so as to make a ball bounce into a target. I'm having trouble getting both the ball and the line to show up concurrently, and I can get only one or the other to appear. It seems to me that the panels block each other out, even though I made them transparent. I would like for them both to appear on the same frame. As of this post, the line panel covers the ball panel.
import javax.swing.Timer;
import java.awt.*;
import javax.swing.*;
import java.awt.event.*;
import java.awt.Graphics;
import java.awt.Color;
import javax.swing.JPanel;
import javax.swing.JFrame;
public class Game
{
public static void main(String args[]) throws Exception
{
JFrame f = new JFrame("Let's Play");
f.setDefaultCloseOperation( JFrame.EXIT_ON_CLOSE );
f.setSize(1280, 720);
f.setLocation(300, 300);
f.setResizable(false);
//this part draws a ball that bounces around the screen
BallPanel ballPanel = new BallPanel()
{
// draw rectangles and arcs
public void paintComponent(Graphics g)
{
super.paintComponent(g); // call superclass's paintComponent
g.setColor(Color.red);
// check for boundaries
if (x < radius) dx = Math.abs(dx);
if (x > getWidth() - radius) dx = -Math.abs(dx);
if (y < radius) dy = Math.abs(dy);
if (y > getHeight() - radius) dy = -Math.abs(dy);
// adjust ball position
x += dx;
y += dy;
g.fillOval(x - radius, y - radius, radius*2, radius*2);
}
};
ballPanel.setOpaque(false);
f.add(ballPanel);
//this part allows you to draw lines on the frame with your mouse
JPanel lineP = new JPanel()
{
Point pointStart = null;
Point pointEnd = null;
{
addMouseListener(new MouseAdapter()
{
public void mousePressed(MouseEvent me)
{
pointStart = me.getPoint();
}
public void mouseReleased(MouseEvent me)
{
pointStart = null;
}
});
addMouseMotionListener(new MouseMotionAdapter()
{
public void mouseMoved(MouseEvent me)
{
pointEnd = me.getPoint();
}
public void mouseDragged(MouseEvent me)
{
pointEnd = me.getPoint();
repaint();
}
});
}
public void paint(Graphics dline)
{
super.paint(dline);
if (pointStart != null)
{
dline.setColor(Color.RED);
dline.drawLine(pointStart.x, pointStart.y, pointEnd.x, pointEnd.y);
}
}
};
lineP.setOpaque(false); //attempted to enable to see ball panel here
f.add(lineP);
f.setVisible(true);
}
}
class BallPanel extends JPanel implements ActionListener
{
private int delay = 10;
protected Timer timer;
public int x = 30; // x position
public int y = 30; // y position
public int radius = 15; // ball radius
public int dx = 10; // increment amount (x coord)
public int dy = 10; // increment amount (y coord)
public BallPanel()
{
timer = new Timer(delay, this);
timer.start(); // start the timer
}
public void actionPerformed(ActionEvent e)
// will run when the timer fires
{
repaint();
}
}
You've got several issues, but the main one is that you're over-using GUI components. You should have just one single component JPanel that does the drawing, a DrawingPanel, and not a ball panel and a line panel. Rather Ball and Line should be logical classes, not GUI classes, and their display should be in the same single DrawingPanel.
Other issues include:
A main method that has way too much code. Most of that code should be off-loaded into the OOP world where it belongs.
GUI component classes that also implement listener interfaces. This is giving the class too much responsibility making debugging and upgrading difficult. Separate these concerns.
One of your classes overrides the paint method, and this should be avoided. Override paintComponent.
The other class that overrides paintComponent has program logic within paintComponent, and this should be avoided since you have limited control over when or if this method gets called. Get the logic out of that class and into either the mouse listener code or the game loop code (Swing Timer).

timer.schedule() in not working at all (in Java)

I made a simple program in Java which draws a rectangle on a canvas. And then the rectangle starts moving along X-axis, from left to right.
But the timer.schedule() function is not working. Following is the code:-
package firstanimation;
import java.awt.*;
import java.util.Timer;
public class FirstAnimation {
public static void main(String[] args) {
Frame frame = new Frame("SomeRandomName");
frame.setBounds(50, 50, 700, 500);
frame.setBackground(Color.red);
MyCanvas canvas = new MyCanvas();
frame.add(canvas);
frame.setVisible(true);
Graphics graph = frame.getGraphics();
Timer timer = new Timer();
Task task = new Task(canvas, graph);
timer.schedule(task, 1000,1000);
}
}
package firstanimation;
import java.awt.*;
public class MyCanvas extends Canvas{
public int x,y,width,height;
public MyCanvas()
{
x = 0;
y = 0;
width = 50;
height = 50;
}
#Override
public void paint(Graphics g) {
g.setColor(Color.LIGHT_GRAY);
g.fillRect(x, y, width, height);
}
#Override
public void update(Graphics g) {
x+=10;
g.fillRect(x, y, width, height);
}
}
package firstanimation;
import java.util.TimerTask;
import java.awt.Graphics;
public class Task extends TimerTask{
private MyCanvas canvas;
private Graphics graphics;
public Task(MyCanvas can, Graphics g)
{
super();
canvas = can;
graphics = g;
canvas.paint(g);
}
#Override
public void run() {
canvas.update(graphics);
//throw new UnsupportedOperationException("Not supported yet."); //To change body of generated methods, choose Tools | Templates.
}
}
But the strange thing is... Every time i'm maximizing and restoring the frame, the box is moving.
Why is this happening?
"But the strange thing is... Every time i'm maximizing and restoring the frame, the box is moving. Why is this happening?"
Because repaint() is called when you resize which update the graphics, which you should be doing, instead of trying to call paint.
But...
Still many things wrong.
Seeing as this is your first animation (package firstanimation;), let me get you started in the right direction.
Don't use Canvas. Use JPanel or JComponent instead. When you do, don't override paint but paintComponent instead. Also make sure you call super.paintComponent so you aren't let with any paint artifact during the animation.
public class MyCanvas extends JPanel {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
//painting code
}
}
Never use getGraphics on a component to do any painting.
You shouldn't ever have to explicitly call paint. The paint[Component] method will be implicitly called for you. A simple call to repaint() will repaint the component.
I just realized you're using all AWT components. Don't use them, they're out-dated. Instead use Swing component. The majority of them are just prefixed with a J, like Frame -> JFrame. They are in the javax.swing.* package.
For animation use a javax.swing.Timer. You can see more at How to Use Timers. The basic construct is
Timer ( int delayInMillis, ActionListener listener )
where delayInMillis is the time to delay between ticks(in this case animations) and the ActionListener listens for "ticks". Each tick, the actionPerformed of the ActionListener is called. There, you can put the code to update any variables you use for animation.
I suggest you read the tutorials Performing Custom Painting to see the proper way to paint.
Here's a simple example with all the points above mentioned
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class AnimateBall extends JPanel {
private static final int D_W = 500;
private static final int D_H = 300;
private Ball ball;
public AnimateBall() {
Random rand = new Random();
int randX = rand.nextInt(D_W);
int randY = rand.nextInt(D_H);
ball = new Ball(randX, randY);
Timer timer = new Timer(15, new ActionListener() {
public void actionPerformed(ActionEvent e) {
ball.animate();
repaint();
}
});
timer.start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
ball.drawBall(g);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(D_W, D_H);
}
public class Ball {
int x = 0;
int y = 0; // Current ball position
int dx = 4; // Increment on ball's x-coordinate
int dy = 4; // Increment on ball's y-coordinate
int radius = 15; // Ball radius
public Ball(int x, int y) {
this.x = x;
this.y = y;
}
Color color = new Color((int) (Math.random() * 256),
(int) (Math.random() * 256), (int) (Math.random() * 256));
public void drawBall(Graphics g) {
g.setColor(color);
g.fillOval(x - radius, y - radius, radius * 2, radius * 2);
}
public void animate() {
if (x < 0 || x > getWidth()) {
dx = -dx;
}
if (y < 0 || y > getHeight()) {
dy = -dy;
}
// Adjust ball position
x += dx;
y += dy;
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame();
frame.add(new AnimateBall());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}

Categories

Resources