I am doing a game in java, but I didnt put image, I already tried to put it by "drawimage", but it is not working right = (
I do not know where would be the best place to save the image and how would be the best method to call the image.`
public void paint(Graphics2D g2){
g2.setColor(getRandomColor());
g2.fillOval((int)x, (int)y, (int)diametro, (int)diametro);
}
`
This code is from my class asteroides they are a circle (because I'm going to test the collision with circles).
Thanks
I'm pretty sure you mean you want do draw an image on the Panel. First of all, I'd recommend you create a resources folder inside the projects src folder, and add all images there. Once you're done, you have to load the image with imageIO and draw it with drawImage. Here's a short example:
package asteroid;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import javax.imageio.ImageIO;
public class Nave {
BufferedImage iconeNave;
public Nave( ... ) {
try{
iconeNave = ImageIO.read(getClass().getResource("/resources/nave.png"));
}catch(IOException e){e.printStackTrace();}
catch(Exception e){e.printStackTrace();}
}
#Override
public void paint(Graphics2D g2){
AffineTransform at = new AffineTransform();
at.translate((int)x + radius/2.5,(int)y + radius/2.5);
at.rotate(Math.PI/2 + angle);
at.translate(-iconeNave.getWidth()/2, -iconeNave.getHeight()/2);
g2.drawImage(iconeNave, at, null);
}
}
In order to create an image in Java, you will first need to create a JPanel (essentially a window on your screen). It will look something like this (taken from javacodex.com):
import java.awt.*;
import java.awt.image.*;
import java.io.*;
import javax.swing.*;
import javax.imageio.ImageIO;
public class JPanelExample {
public static void main(String[] arguments) throws IOException {
JPanel panel = new JPanel();
BufferedImage image = ImageIO.read(new File("./java.jpg"));
JLabel label = new JLabel(new ImageIcon(image));
panel.add(label);
// main window
JFrame.setDefaultLookAndFeelDecorated(true);
JFrame frame = new JFrame("JPanel Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
// add the Jpanel to the main window
frame.add(panel);
frame.pack();
frame.setVisible(true);
}
}
However, I am assuming that by "image," you mean a shape drawn out onto the screen instead (since you are using a paint method that draws a circle in your example). Then, you have your code in the correct method but need to implement a JPanel (since without a JPanel or JFrame you can't really display anything onto the screen graphics-wise).
Here is an example taken from Board.java of my Atari Breakout mini-game:
public class Board extends JPanel implements KeyListener {
// Other Game Code
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.BLACK);
// Shows lives left and score on screen
g2.drawString("Lives left: " + lives, 400, 600);
g2.drawString("Score: " + score, 400, 500);
// Paints Walls (separate paint method created in wall class)
topWall.paint(g2);
bottomWall.paint(g2);
leftWall.paint(g2);
rightWall.paint(g2);
// Paints 2 Balls (separate paint method created in ball class)
b.paint(g2);
b2.paint(g2);
// Paints Paddle (separate paint method created in paddle class)
paddle.paint(g2);
// Paints Bricks based on current level (separate paint method created in brick class)
// Bricks were created/stored in 2D Array, so drawn on screen through 2D Array as well.
if (level == 1) {
for (int x = 0; x < bricks.length; x++) {
for (int i = 0; i < bricks[0].length; i++) {
bricks[x][i].paint(g2);
}
}
}
if (level == 2) {
for (int x = 0; x < bricks.length; x++) {
for (int i = 0; i < bricks[0].length; i++) {
bricks[x][i].paint(g2);
}
}
}
}
Here is an example of a Paint Method in the Paddle Class:
// Constructor
public Paddle(int x, int y, int w, int h){
xpos = x;
ypos = y;
width = w;
height = h;
r = new Rectangle(xpos, ypos, width, height);
}
public void paint(Graphics2D g2){
g2.fill(r);
}
Output (What is shown on Screen):
Hope this helps you in drawing your circle!
Related
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();
}
}
}
package com.company;
import javax.swing.*;
import javax.swing.border.Border;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayDeque;
import java.util.Random;
public class REcom {
REcom() {
JFrame jfm = new JFrame("Paint");
jfm.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
BorderLayout border = new BorderLayout();
border.setVgap(10);
jfm.setLayout(border);
DrawPanel dw = new DrawPanel();
dw.addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
dw.setXY(e.getX() , e.getY());
dw.repaint();
}
#Override
public void mouseMoved(MouseEvent e) {
dw.previosPosition = new Position(e.getX() , e.getY());
}
});
jfm.add(dw ,BorderLayout.CENTER);
jfm.setBackground(Color.white);
jfm.setCursor(Cursor.getPredefinedCursor(Cursor.CROSSHAIR_CURSOR));
jfm.setSize(500 ,500);
JPanel color = new JPanel(new FlowLayout(FlowLayout.LEFT));
//that Jpanel background is doubled
// color.setBounds(new Rectangle(0 ,0 , 100 , jfm.getHeight()));
Button blue = new Button();
blue.setBackground(Color.blue);
blue.setSize(500 ,200);
blue.addActionListener(e -> {
dw.color = Color.blue;
});
color.add(blue);
Button white = new Button();
white.setBackground(Color.white);
white.setSize(200 ,200);
white.addActionListener(e -> {
dw.color = Color.white;
});
color.add(white);
jfm.add(color , BorderLayout.NORTH);
jfm.setPreferredSize(new Dimension(500 ,500));
jfm.pack();
color.setBackground(Color.blue);
jfm.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(REcom::new);
}
}
class DrawPanel extends JPanel {
ArrayDeque<Position> ad = new ArrayDeque<>();
Position previosPosition = null;
Color color = Color.yellow;
void setXY(int x , int y) {
ad.push(new Position(x , y));
}
#Override
protected void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
g2.setColor(color);
g2.setStroke(new BasicStroke(12f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER));
Position d = ad.pollLast();
if(d != null) {
if(previosPosition == null)
g2.fillOval(d.x, d.y -10 , d.x , d.y-10);
else{
g2.drawLine(previosPosition.x -5 , previosPosition.y -10 , d.x-5 , d.y-10);
}
previosPosition = d;
}
}
}
class Position {
int x, y;
Position(int x, int y) {
this.x= x;
this.y = y;
}
void setXY(int x , int y) {
this.x = x;
this.y = y;
}
}
about code: two panels, one intended for drawing, another for the switch between colors. I decided to add the switcher to the top in frame. But its background move to the bottom and I can draw on it.(therefore its background out of border). and if I will set a border for central JPanel its panel (for drawing), top of the border is doubled
I wanted to add Jpanel to the top for switch between colors (background is doubled after drawing anything )for drawing. But the top Jpanel background is doubled. But if I will replace Jpanel (color) by the common panel, the program work normally. I don't know why? Please help! if you don't understand please try run code (this question I wrote with translater)
JPanel background is doubled
When you do custom painting, the first statement in your method should be:
super.paintComponent(g);
to clear the background otherwise you can have painting artifacts, which is why you see the two blue lines.
Of course when you do add the super.paintComponent(g) the painting disappears because the background is cleared.
The are two solutions to the problem:
keep an ArrayList of object to paint and the iterate through the ArrayList in the paintComponent() method to paint all the objects
paint to a BufferedImage, then just draw the entire image.
I would suggest that option 2 might be the best in this case.
Check out Custom Painting Approaches for more information and examples of each approach.
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;
}
}
package Game;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.*;
import Maps.*;
public class Window extends JFrame implements KeyListener
{
private Insets insets;
private int currentMapX;
private int currentMapY;
public Window()
{
super();
setSize(new Dimension(1920, 1080));
setLayout(null);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setUndecorated(true);
setFocusable(true);
setContentPane(new Container());
setBackground(Color.BLACK);
addKeyListener(this);
}
public void startGame()
{
insets = getInsets();
currentMapX = 960 - (Game.level_01.getWidth() / 2);
currentMapY = 540 - (Game.level_01.getHeight() / 2);
Game.level_01.setBounds(currentMapX, currentMapY, Game.level_01.getWidth(), Game.level_01.getHeight());
add(Game.level_01);
}
private void moveMapRight()
{
Game.level_01.setBounds(++currentMapX, currentMapY, Game.level_01.getWidth(), Game.level_01.getHeight());
}
private void moveMapLeft()
{
Game.level_01.setBounds(--currentMapX, currentMapY, Game.level_01.getWidth(), Game.level_01.getHeight());
}
public void paint(Graphics g)
{
super.paint(g);
Graphics2D g2d = (Graphics2D)g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
Game.player.paint(g2d);
}
public void keyPressed(KeyEvent k)
{
if(k.getKeyCode() == KeyEvent.VK_RIGHT) moveMapRight();
if(k.getKeyCode() == KeyEvent.VK_LEFT) moveMapLeft();
}
public void keyReleased(KeyEvent k){}
public void keyTyped(KeyEvent k){}
}
I've got the first class that extends the JFrame and contains the following class.
package Maps;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.RenderingHints;
import java.io.*;
import java.util.*;
import javax.swing.*;
import Game.*;
import Tiles.*;
public class Level extends JPanel
{
protected final File txt_MAP;
protected final ArrayList<Tile> jLabel_MAP;
protected final ArrayList<Integer> linesLength;
protected Insets insets = Game.window.getInsets();
protected int arrayIndex;
protected int leftIndex;
protected int topIndex;
protected int width;
protected int height;
public Level(File f)
{
super();
setLayout(null);
setBackground(Color.BLACK);
leftIndex = 0;
topIndex = 0;
txt_MAP = f;
jLabel_MAP = new ArrayList<>();
linesLength = new ArrayList<>();
arrayIndex = 0;
readTxt();
width = linesLength.get(0) * Game.tileSize;
height = linesLength.size() * Game.tileSize;
addTiles();
}
private void readTxt()
{
BufferedReader br = null;
try
{
br = new BufferedReader(new FileReader(txt_MAP));
}
catch(FileNotFoundException e){}
try
{
String line = br.readLine();
while(line != null)
{
String[] words = line.split(" ");
for(int a = 0; a < words.length; a++)
{
for(int b = 0; b < Game.tilesIcons.length; b++)
{
if(Game.tilesList[b].getName().equals(words[a] + ".gif"))
{
if(Game.tilesList[b].getName().contains(Game.grass_TYPE)) jLabel_MAP.add(arrayIndex, new Grass(Game.tilesIcons[b]));
if(Game.tilesList[b].getName().contains(Game.soil_TYPE)) jLabel_MAP.add(arrayIndex, new Soil(Game.tilesIcons[b]));
if(Game.tilesList[b].getName().contains(Game.sky_TYPE)) jLabel_MAP.add(arrayIndex, new Sky(Game.tilesIcons[b]));
arrayIndex++;
}
}
}
linesLength.add(words.length);
line = br.readLine();
}
}
catch(IOException e){}
}
private void addTiles()
{
for(int i = 0; i < jLabel_MAP.size(); i++)
{
jLabel_MAP.get(i).setBorder(null);
jLabel_MAP.get(i).setBounds(insets.left + leftIndex, insets.top + topIndex, 64, 64);
add(jLabel_MAP.get(i));
if(leftIndex == width - Game.tileSize)
{
leftIndex = 0;
topIndex += 64;
}
else leftIndex += 64;
}
}
public int getWidth()
{
return width;
}
public int getHeight()
{
return height;
}
}
This class extends JPanel and contains an arrayList of Jlabels.
package Player;
import java.awt.*;
import Game.*;
public class Player
{
private int x;
private int y;
private int xa;
private int ya;
private Graphics2D g2d;
public Player(double x, double y)
{
super();
this.x = (int)x;
this.y = (int)y;
xa = 0;
ya = 1;
}
public void movePlayer()
{
x += xa;
y += ya;
}
public void setDirection(int xa, int ya)
{
this.xa = xa;
this.ya = ya;
}
public void paint(Graphics g)
{
g2d = (Graphics2D)g;
g2d.drawImage(Game.playerImages[6], x , y, Game.tileSize, Game.tileSize, null);
}
public int getX()
{
return x;
}
public int getY()
{
return y;
}
public int getXA()
{
return xa;
}
public int getYA()
{
return ya;
}
}
Finally this class is the class for the player, that is a BufferedImage. My problem is that when I start the program, the player image starts flickering, because when I call the paint() method in the JFrame class, this paints the first the jpanel and then the player image. How can I solve the Image flickering?
Thanks in advance.
As others have said, you shouldn't override paint(Graphics g) in top-level components, so that's part of the problem, but you also need to be careful to make sure you only paint to the screen once per repaint:
My problem is that when I start the program, the player image starts
flickering, because when I call the paint() method in the JFrame
class, this paints the first the jpanel and then the player image.
What's happening now is, every time you call a method that modifies the Graphics object in your paint(Graphics screen) method, it's directly modifying the screen contents, forcing the screen to refresh before you've finished drawing what you really wanted to - the Player. By first painting to super, then again with your custom rendering, you're actually painting to screen at least twice, causing the flicker. You can fix this by double buffering.
Double Buffering involves first rendering to an image, then painting that image to screen. Using built-in methods provided by the Component class (remember that JPanel extends Component), you can get the size of the viewable area of your component. Create a BufferedImage of the same size, then call bufferedImage.createGraphics() - this will give you a Graphics2D object that you can use to draw onto your bufferedImage with. Once you're done rendering to bufferedImage, call screen.drawImage(bufferedImage,0,0,null). This allows you to modify the bufferedImage as many times as you want without actually doing a screen refresh, then draw the contents of the buffer to screen in a single call.
ADDITIONAL INFO
I should have pointed out earlier that I know nothing about how things are actually laid out on screen for you, so you may need to do some additional checks on the bounds of your Player's screen area and where the upper left should be placed in the call to drawImage(Image,x,y,ImageObserver). Also be aware of transparency or the lack thereof in your BufferedImage - you can easily get lost if you paint opaque pixels over other important stuff on screen.
TRY THIS (but don't keep it)
Again, I don't recommend doing all of your painting in top-level components, but you could try doing something like this in the interim:
//This is in the Window class:
public void paint(Graphics screen)
{
//Render everything first to a BufferedImage:
BufferedImage bufferedImage = ... //Initialize based on Window's
//size and bounds.
Graphics2D buf = bufferedImage.createGraphics();
super.paint(buf);
buf.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
Game.player.paint(buf);
//Now paint the buffer to screen:
int x = ... //set to top-left x-coordinate, probably 0
int y = ... //set to top-left y-coordinate, probably 0
screen.drawImage(buf,x,y,null);
}
Don't override paint(Graphics) in a top level container like JFrame (which is not double buffered). Instead do custom painting in a JPanel (which is double buffered).
Also, when overriding any of the paint methods, immediately call the super method thereby painting the background and effectively erasing the earlier drawing(s).
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.