Stop JPanel's paintComponent method from painting the background - java

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();
}
}
}

Related

Keeping Shapes on Screen Help, can't figure out how to keep track of X,Y coordinates

I have tried and tried, I looked up many examples for keeping Shapes on the screen but can't seem to adapt to my code. In Summary, a left click prints a square, a right click prints a circle. I would like to fill the window with squares (rects) and circles. Any help and explanation so I can learn the concept would be great. I understand I have to keep track on the coordinates, perhaps in a loop but can seem to get it to work. Thanks again.
import java.awt.*;
import javax.swing.JFrame;
import java.awt.event.MouseListener;
import java.awt.event.MouseEvent;
public class MouseButtonTester extends JFrame implements MouseListener
{
private int mouseX, mouseY;
private int mouseButton;
private boolean isFirstRun;
private static final int WIDTH = 640;
private static final int HEIGHT = 480;
private static final long serialVersionUID = 0; //use this if you do not like warnings
public MouseButtonTester() //constructor
{
super("Mouse Button Tester");
//set up all variables
mouseX = mouseY = 0;
mouseButton = 0;
isFirstRun = true;
//set up the Frame
setSize(WIDTH,HEIGHT);
setBackground(Color.WHITE);
setVisible(true);
//start trapping for mouse clicks
addMouseListener(this);
}
public void mouseClicked(MouseEvent e)
{
mouseX=e.getX(); //Tracks x coordinates
mouseY=e.getY(); //Tracker y coordinates
mouseButton = e.getButton(); //gets button number
repaint();
}
public void paint( Graphics window ) // Draws the Window
{
if(isFirstRun)
{
window.setColor(Color.WHITE);
window.fillRect(0,0,WIDTH, HEIGHT);
//change isFirstRun
}
window.setFont(new Font("TAHOMA",Font.BOLD,12));
window.setColor(Color.BLUE);
window.drawString("MOUSE BUTTON TESTER", 420,55);
draw(window);
}
public void draw(Graphics window)
{
if(mouseButton==MouseEvent.BUTTON1) //left mouse button pressed
{
//window.drawString("BUTTON1", 50,200); //debug code
window.setColor(Color.RED);
window.drawRect(mouseX,mouseY,10,10);
}
//right mouse button pressed
{
if (mouseButton == MouseEvent.BUTTON2)
window.setColor(Color.BLUE);
window.drawOval(mouseX,mouseY,10,10);
}
//any other mouse button pressed
{
}
}
public void mouseEntered(MouseEvent e) { }
public void mouseExited(MouseEvent e) { }
public void mousePressed(MouseEvent e) {
}
public void mouseReleased(MouseEvent e) { }
}
------ Main Method --------------
public class MouseButtonTesterRunner
{
public static void main(String[] args)
{ MouseButtonTester prog = new MouseButtonTester();
}
}
First, start by having a read through:
Performing Custom Painting
Painting in AWT and Swing
So you can get a understanding how painting in Swing works, how you can work with it and your responsibilities when doing so.
Next, have a read through:
How can I set in the midst?
Java JFrame .setSize(x, y) not working?
How to get the EXACT middle of a screen, even when re-sized
Graphics rendering in title bar
for reasons why you should avoid overriding paint of top level containers like JFrame
Finally...
Painting in Swing is destructive, that is, every time your component is painted, you are expected to completely repaint the component state from scratch.
In order to achieve your goal, you will need to maintain a cache of the items you want to paint.
The concept itself it's very difficult, but there might be some "gotchas" along the way
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.FontMetrics;
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 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 TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private List<Point> circles;
private List<Point> squares;
public TestPane() {
circles = new ArrayList<>();
squares = new ArrayList<>();
addMouseListener(new MouseAdapter() {
#Override
public void mouseReleased(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
circles.add(e.getPoint());
} else if (SwingUtilities.isRightMouseButton(e)) {
squares.add(e.getPoint());
}
repaint();
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// I'm picky
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.RED);
for (Point p : circles) {
g2d.drawOval(p.x, p.y, 10, 10);
}
g2d.setColor(Color.BLUE);
for (Point p : squares) {
g2d.drawRect(p.x, p.y, 10, 10);
}
g2d.setFont(new Font("TAHOMA", Font.BOLD, 12));
g2d.setColor(Color.BLUE);
FontMetrics fm = g2d.getFontMetrics();
String text = "MOUSE BUTTON TESTER";
int x = getWidth() - fm.stringWidth(text) - 10;
int y = getHeight() - (fm.getAscent() - fm.getHeight()) - 10;
g2d.drawString(text, x, y);
g2d.dispose();
}
}
}
I suggest creating 2 classes.
1) Circle class
2) Square Class
Those classes will store info that you need, like X, y etc..
Initialize an array list that stores those objects & read from it in your paint method, proceed with painting them just like you do in your code.
(On a click event you simply create new object (circle/square) and add it into your array list)
So here's how i understand how your code works so far: The user left clicks, those coordinates are recorded, and a square is rendered on the screen at those coordinates.
When we click the coordinates are updated and on the next draw, the square is moved to a new position.
You were on the right track about needing a loop.
Here's the logic you need to implement:
Create an ArrayList as a member variable. The type can be a pair<int,int> object. So this arraylist will hold a list of X,Y coordinates. This arraylist will look something like this:
ArrayList<pair<int,int>> myRightClickCoords;
Once you make that list, every time the user clicks, record the click coordinates and insert them into the arraylist. That will look something like this:
myRightClickCoords.insert(new pair<int,int>(e.getX(),e.getY()));
Then, once that is added to your code, in your draw function, you can have a look that runs through the entire myRightClickCoords list and runs drawRect for each set of coordinates.
Once you get that working, you can do the same thing for left click and circles. Good luck!

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

Java Applet - Stop other threads from changing Graphics color

as part of a school project we have to create a little game using Applets. I'm working on some tests right now but there's one thing I can't quite figure out:
I want to have multiple objects flying on my screen at the same time on my Applet screen. The animation effect is created by drawing the object, deleting it then moving it after a while.
Here's my code:
Robotworld class
package core;
import items.Obstacle;
import java.applet.Applet;
import java.awt.*;
import java.util.ArrayList;
public class Roboterwelt extends Applet {
private ArrayList<Obstacle> obstacles = new ArrayList<>();
#Override
public void init() {
setSize(600, 600);
Graphics g = getGraphics();
g.setColor(Color.BLACK);
for(int x = 0; x < 5; x++) {
Obstacle h = new Obstacle((x+1)*100, 100, g, this);
obstacles.add(h);
Thread t = new Thread(h);
t.start();
}
}
#Override
public void paint(Graphics g) {
for(Obstacle o : obstacles) {
o.draw();
}
}
}
Obstacle class
package items;
import java.applet.Applet;
import java.awt.*;
public class Obstacle implements Runnable {
private int x;
private int y;
private Graphics g;
public Hindernis(int x, int y, Graphics g) {
this.x = x;
this.y = y;
this.g = g;
}
public void draw() {
g.drawOval(x, y, 50, 50); //Draw obstacle
}
//Deleting the obstacle by covering it with a white circle
public void delete() {
g.setColor(Color.WHITE); //Change the color to white
g.fillOval(x-5,y-5,60,60); //Making it a bit bigger than the obstacle to fully cover it
g.setColor(Color.BLACK); //Reset the color to black
}
#Override
public void run() {
try {
while(y < 600) {
delete();
y += 10;
draw();
Thread.sleep(1000);
}
} catch(InterruptedException e) {
e.printStackTrace();
}
}
}
The problem is the part where I change the color of the Graphics object to cover the circle in white. When I have multiple threads running to represent the multiple obstacles on my screen and redrawing AND deleting happens concurrently, a thread gets interrupted after changing the color to white and draws a filled oval with the Graphics object which color was set to black by another thread that ran the delete() method to the end.
How can I force the program to not interrupt the delete() method between the color change to white and the drawing of the filled oval shape?
Disclaimer
Applet is deprecated, it is no longer supported by browsers, Oracle or the community. It would be unprofessional of me to try and encourage you to keep using them.
I appreciate that this is a "school" assignment, but perhaps it's time your instructor caught up with the rest of the world and started using something which doesn't actual cause more issues then it solves (hint JavaFX) - IMHO
Answer...
Don't use getGraphics, this is not how custom painting should be done. Painting should be done within the confines of the paint methods. Take a look at Painting in AWT and Swing for details. Apart from solving your immediate issue, your current approach risks been "wiped" clean when the applet repaints itself.
Overriding paint of the top level containers like Applet is a bad idea. Apart from locking you into a single use case, they aren't double buffered, which will cause flickering when painting occurs. The simplest solution is to start with a JPanel, which is double buffered and which can be added to what ever container you want to use.
You don't need multiple threads. Thread is a bit of an art form. More threads doesn't always mean more work gets done and can actually degrade the performance of the system. In your case you want to "update" the state in a single pass and then schedule a paint pass, so that the operations are synchronised in a single step and you don't end up with "dirty" updates
The following example simple makes use of Swing, which is based on AWT. It uses a JFrame instead of an Applet, but the concept is easily transferable, because the core functionality is based on a JPanel, so you can add it to what ever you want.
It makes use of a Swing Timer, which basically schedules a callback on a regular bases, but does it in away which makes it safe to update the state of the UI from (this replaces your Thread).
By using paintComponent to paint the Obstacles, we get two things for free.
Double buffering, so no more flickering
The Graphics context is automatically prepared for us, we don't need to "delete" the objects first, we simply paint the current state
The example also removes the Obstacle once it passes the edge of the panel, so you don't waste time trying to move/paint it when it's no longer visible.
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class TestPane extends JPanel {
private List<Obstacle> obstacles;
public TestPane() {
Color[] colors = new Color[]{Color.RED, Color.GREEN, Color.BLUE, Color.MAGENTA, Color.YELLOW};
obstacles = new ArrayList<>(10);
int y = 0;
for (int index = 0; index < 5; index++) {
y += 55;
Obstacle obstacle = new Obstacle(y, 0, colors[index]);
obstacles.add(obstacle);
}
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
Iterator<Obstacle> it = obstacles.iterator();
while (it.hasNext()) {
Obstacle ob = it.next();
if (ob.move(getSize())) {
it.remove();
}
}
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
Iterator<Obstacle> it = obstacles.iterator();
while (it.hasNext()) {
Obstacle ob = it.next();
ob.paint(g2d);
}
g2d.dispose();
}
}
public class Obstacle {
private int x, y;
private Color color;
public Obstacle(int x, int y, Color color) {
this.x = x;
this.y = y;
this.color = color;
}
public void paint(Graphics2D g2d) {
g2d.setColor(color);
g2d.fillRect(x, y, 50, 50);
}
public boolean move(Dimension size) {
y += 1;
return y > size.height;
}
}
}
But all the Obstacles move at the same rate!
Yeah, that's because you used a single delta. If you want the Obstacles to move at different rates, then change the deltas, for example...
public static class Obstacle {
private static Random RND = new Random();
private int x, y;
private Color color;
private int yDelta;
public Obstacle(int x, int y, Color color) {
this.x = x;
this.y = y;
this.color = color;
yDelta = RND.nextInt(5) + 1;
}
public void paint(Graphics2D g2d) {
g2d.setColor(color);
g2d.fillRect(x, y, 50, 50);
}
public boolean move(Dimension size) {
y += yDelta;
return y > size.height;
}
}

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.

Stop flickering in simple Java animation

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.

Categories

Resources