Next semester we have a module in making Java applications in a team. The requirement of the module is to make a game. Over the Christmas holidays I've been doing a little practice, but I can't figure out the best way to draw graphics.
I'm using the Java Graphics2D object to paint shapes on screen, and calling repaint() 30 times a second, but this flickers terribly. Is there a better way to paint high performance 2D graphics in Java?
What you want to do is to create a canvas component with a BufferStrategy and render to that, the code below should show you how that works, I've extracted the code from my self written Engine over here.
Performance solely depends on the stuff you want to draw, my games mostly use images. With around 1500 of them I'm still above 200 FPS at 480x480. And with just 100 images I'm hitting 6k FPS when disabling the frame limiting.
A small game (this one has around 120 images at once at the screen) I've created can be found here (yes the approach below also works fine as an applet.)
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.GraphicsConfiguration;
import java.awt.GraphicsEnvironment;
import java.awt.Toolkit;
import java.awt.Transparency;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import javax.swing.JFrame;
import javax.swing.WindowConstants;
public class Test extends Thread {
private boolean isRunning = true;
private Canvas canvas;
private BufferStrategy strategy;
private BufferedImage background;
private Graphics2D backgroundGraphics;
private Graphics2D graphics;
private JFrame frame;
private int width = 320;
private int height = 240;
private int scale = 1;
private GraphicsConfiguration config =
GraphicsEnvironment.getLocalGraphicsEnvironment()
.getDefaultScreenDevice()
.getDefaultConfiguration();
// create a hardware accelerated image
public final BufferedImage create(final int width, final int height,
final boolean alpha) {
return config.createCompatibleImage(width, height, alpha
? Transparency.TRANSLUCENT : Transparency.OPAQUE);
}
// Setup
public Test() {
// JFrame
frame = new JFrame();
frame.addWindowListener(new FrameClose());
frame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
frame.setSize(width * scale, height * scale);
frame.setVisible(true);
// Canvas
canvas = new Canvas(config);
canvas.setSize(width * scale, height * scale);
frame.add(canvas, 0);
// Background & Buffer
background = create(width, height, false);
canvas.createBufferStrategy(2);
do {
strategy = canvas.getBufferStrategy();
} while (strategy == null);
start();
}
private class FrameClose extends WindowAdapter {
#Override
public void windowClosing(final WindowEvent e) {
isRunning = false;
}
}
// Screen and buffer stuff
private Graphics2D getBuffer() {
if (graphics == null) {
try {
graphics = (Graphics2D) strategy.getDrawGraphics();
} catch (IllegalStateException e) {
return null;
}
}
return graphics;
}
private boolean updateScreen() {
graphics.dispose();
graphics = null;
try {
strategy.show();
Toolkit.getDefaultToolkit().sync();
return (!strategy.contentsLost());
} catch (NullPointerException e) {
return true;
} catch (IllegalStateException e) {
return true;
}
}
public void run() {
backgroundGraphics = (Graphics2D) background.getGraphics();
long fpsWait = (long) (1.0 / 30 * 1000);
main: while (isRunning) {
long renderStart = System.nanoTime();
updateGame();
// Update Graphics
do {
Graphics2D bg = getBuffer();
if (!isRunning) {
break main;
}
renderGame(backgroundGraphics); // this calls your draw method
// thingy
if (scale != 1) {
bg.drawImage(background, 0, 0, width * scale, height
* scale, 0, 0, width, height, null);
} else {
bg.drawImage(background, 0, 0, null);
}
bg.dispose();
} while (!updateScreen());
// Better do some FPS limiting here
long renderTime = (System.nanoTime() - renderStart) / 1000000;
try {
Thread.sleep(Math.max(0, fpsWait - renderTime));
} catch (InterruptedException e) {
Thread.interrupted();
break;
}
renderTime = (System.nanoTime() - renderStart) / 1000000;
}
frame.dispose();
}
public void updateGame() {
// update game logic here
}
public void renderGame(Graphics2D g) {
g.setColor(Color.BLACK);
g.fillRect(0, 0, width, height);
}
public static void main(final String args[]) {
new Test();
}
}
The flickering is due to you writing direct to the screen. Use a buffer to draw on and then write the entire screen in 1 go. This is Double Buffering which you may have heard of before. Here is the simplest form possible.
public void paint(Graphics g)
{
Image image = createImage(size + 1, size + 1);
Graphics offG = image.getGraphics();
offG.setColor(Color.BLACK);
offG.fillRect(0, 0, getWidth(), getHeight());
// etc
See the use of the off screen graphics offG. It is expensive to create the off screen image so I would suggest creating it only on first call.
There's other areas you can improve this further eg creating a compatible image, using clipping etc. For more fine tuned control of animation you should look into active rendering.
There's a decent page I have bookmarked discussing game tutorials here.
Good luck!
Java OpenGL (JOGL) is one way.
I think you made an override from paint(Graphics g)? This is not the good way. Use the same code but in paintComponent(Graphics g) instead of paint(Graphics g).
A label you can search is doublebuffer. That is what will be done automatically by overriding paintComponent.
There is a simple way to optimize your program. Get rid of any complex code and just use JComponent instead Canvas and paint your objects on it. Thats all. Enjoy it...
Related
I am trying to create spinning image Animation but something seems to be not working in the code. I am rotating image at various angles and drawing it but at the end I only end up single rotated image than animation. Is this possible to do in Java or do I need switch to C# Unity where I found multiple examples on doing so nothing so far in Java. I am new to Swing so I would really appreciate simplified answer.
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.util.concurrent.TimeUnit;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Rotate extends JPanel {
public static void main(String[] args) {
new Rotate().go();
}
public void go() {
JFrame frame = new JFrame("Rotate");
JButton b = new JButton("click");
MyDrawPanel p = new MyDrawPanel();
frame.add(p);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(1000, 1000);
frame.setVisible(true);
for(int i = 0; i < 300; i++) {
try {
TimeUnit.MILLISECONDS.sleep(50);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
repaint();
}
}
class MyDrawPanel extends JPanel{
Image image = new ImageIcon(
getClass()
.getResource("wheel.png"))
.getImage();
public void animateCircle(Graphics2D g2d ) {
//g2d = (Graphics2D) g2d.create();
g2d.rotate(Math.toRadians(25), 250, 250);
g2d.drawImage(image, 0, 0, 500, 500, this);
}
#Override
protected void paintComponent(Graphics g) {
//super.paintComponent(g);
g.fillRect(0, 0, this.getWidth(), this.getHeight());
Graphics2D g2d = (Graphics2D) g;
animateCircle(g2d);
}
}
}
I tried moving for loop in the paintComponent() method but it didn't help either.
Here
public void animateCircle(Graphics2D g2d ) {
//g2d = (Graphics2D) g2d.create();
g2d.rotate(Math.toRadians(25), 250, 250);
g2d.drawImage(image, 0, 0, 500, 500, this);
}
You rotation is fixed, so you aren't seeing your image spinning
By changing your value in Math.toRadians(...), you can make it appear to spin
This
for(int i = 0; i < 300; i++) {
rotationStep ++;
try {
TimeUnit.MILLISECONDS.sleep(50);
} catch (InterruptedException e1) {
e1.printStackTrace();
}
repaint();
}
}
Is the wrong way to do animation in swing. The right way is to use a javax.swing.Timer
public class MyTimer implements ActionListener {
int rotationStep = 0;
public void actionPerformed(ActionEvent ae) {
// for(int i = 0; i < 300; i++) {
rotationStep ++;
repaint();
}
}
You can do it like this. I reorganized some of your code which you can of course change at your leisure. Most of the critical elements are documented within the code. Major changes include:
eliminating magic numbers - allows alterations to happen in one place
using Rendering hints to eliminate rotating images.
overridding getPreferredSize() in the panel class
computing panel size to allow full rotation within panel.
using a swing timer to control repaint and angle updates
public class Rotate extends JPanel {
BufferedImage image = null;
public static void main(String[] args) {
new Rotate().go();
}
public void go() {
JFrame frame = new JFrame("Rotate");
JButton b = new JButton("click");
File file = new File("wheel.png");
try {
image = ImageIO
.read(new FileInputStream(new File("H:/Bench.jpg")));
} catch (IOException ioe) {
ioe.printStackTrace();
}
// invoke an instance of the panel with the image
MyDrawPanel p = new MyDrawPanel();
frame.add(p);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
// center the frame on the screen
frame.setLocationRelativeTo(null);
frame.setVisible(true);
// start the animation
p.animateCircle();
}
class MyDrawPanel extends JPanel {
double angle = 0;
//increment of the angle of rotation
double inc = Math.toRadians(.1);
int imageWidth;
int imageHeight;
int panelWidth;
int panelHeight;
double ctrX;
double ctrY;
int startX;
int startY;
#Override
public Dimension getPreferredSize() {
return new Dimension(panelWidth, panelHeight);
}
public MyDrawPanel() {
double imageWidth = image.getWidth();
double imageHeight = image.getHeight();
setBackground(Color.WHITE);
// compute panel size to allow full rotation within by computing
//the image's diagonal length
panelWidth = (int)Math.hypot(imageWidth, imageHeight);
panelHeight = panelWidth;
//target location for writing object (upper left corner)
startX = (int)(panelWidth-imageWidth)/2;
startY = (int)(panelHeight-imageHeight)/2;
// center of rotation
ctrX = panelWidth/2;
ctrY = panelHeight/2;
}
// This starts the animation using a swing timer to update the angle and repaint the image
public void animateCircle() {
Timer timer = new Timer(0, (ae)-> {
angle += inc; repaint();
});
timer.setDelay(10);
timer.start();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
// setting the rendering hints allows smooth rotation of images with minimal distortion
g2d.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BICUBIC);
// rotate the graphics context about the center
g2d.rotate(angle, ctrX, ctrY);
// draw the image. Top left at startX and startY
g2d.drawImage(image, startX, startY, this);
}
}
}
Note: If the timer is set to a low value (faster rotation) and the image is too large, the image may not finish the current rotation cycle before the timer re-fires.
After a day of searching, I have given up and decided to ask this question: I can't seem to stop the constant flickering of this program:
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class Paint {
BufferedImage image0, image1, image2;
double rotation0 = 0.0001;
double rotation1 = 0.0501;
double rotation2 = 3.0001;
double add0 = 0.0001;
double add1 = 0.0016;
double add2 = 0.000001;
private int x() {
return Main.getX() / 2;
}
private int y() {
return Main.getY() / 2;
}
public Paint() {
try {
image0 = ImageIO.read(new File("circle1.jpg"));
image1 = ImageIO.read(new File("circle2.jpg"));
image2 = ImageIO.read(new File("circle3.jpg"));
} catch (IOException e) {
e.printStackTrace();
}
}
public void paint(Graphics g) {
// create the transform, note that the transformations happen
// in reversed order (so check them backwards)
AffineTransform at0 = new AffineTransform();
AffineTransform at1 = new AffineTransform();
AffineTransform at2 = new AffineTransform();
// 4. translate it to the center of the component
at0.translate(x(), y() + 10);
at1.translate(x(), y() + 10);
at2.translate(x(), y() + 10);
// 3. do the actual rotation
rotation0 += add0;
rotation1 += add1;
rotation2 += add2;
at0.rotate(rotation0);
at1.rotate(rotation1);
at2.rotate(rotation2);
// 2. just a scale because this image is big
at0.scale(1, 1);
at1.scale(1, 1);
at2.scale(1, 1);
// 1. translate the object so that you rotate it around the
// center (easier :))
at0.translate(-image0.getWidth()/2, -image0.getHeight()/2);
at1.translate(-image1.getWidth()/2, -image1.getHeight()/2);
at2.translate(-image2.getWidth()/2, -image2.getHeight()/2);
// draw the image
Graphics2D g2d = (Graphics2D) g;
g2d.drawImage(image0, at0, null);
g2d.drawImage(image1, at1, null);
g2d.drawImage(image2, at2, null);
}
}
IMPORTANT: Everything works but the double buffering, which causes the image to flicker. If you don't mind, please provide code along with the answer.
Double buffering consists into drawing your content to an internal image and then draw that image on screen. By doing that, the image is never partially printed.
So, instead of drawing your 3 images directly in your Graphics object, try drawing them into a new BufferedImage and draw that image in the Graphics object (screen).
See this official page for more info: https://docs.oracle.com/javase/tutorial/extra/fullscreen/doublebuf.html
I assume the usage is in a JPanel child. By default a JPanel is double-buffered.
JPanel panel new JPanel() {
Paint paint = new Paint();
#Override
public void paintComponent(Graphics g) {
paint.paint(g);
}
};
.. add the panel to the JFrame.
This should work in java swing. Java awt components is another obsolete matter.
I am making Pac-Man and I'm having trouble with drawing graphics on a frame, when i draw my point image it looks like a game of snake, i tried putting my drawing methods for background and char both in the render method, but than my point image flickers
What it currently looks like, feel free to ignore the random face it was an inside joke.
Also this is my very first game so any tips on structure, pointers on what I am doing right (if anything) and what I'm doing wrong, and general tips would be extremely helpful!
Also I am aware that i have a couple unused methods
Code:
package game;
import graphics.map;
import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;
public class main extends Canvas implements Runnable{
private static final long serialVersionUID = 1L; //not sure why it wanted me to do this, maybe ask bender, or just google it later
public static boolean running = false;
public static int HEIGHT = 800;
public static int WIDTH = 600;
public static int posX = 50;
public static int posY = 50;
public static final String name = "Pac Man Alpha 1.4";
private static final double speed = 1.2;
public input input;
static BufferedImage background = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);;
static BufferedImage pacman = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);;
static BufferedImage settingsBackground = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);;
static BufferedImage level1 = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);;
static BufferedImage level2 = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);;
static BufferedImage points = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);;
static BufferedImage point = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);;
static JFrame frame;
private input keypress = new input();
private map map;
private static boolean charLoaded = false;
public static boolean MAIN_MENU = true;
public static boolean GAME = false;
public static boolean level1test = true;
public static boolean level2test = false;
public static boolean level3test = false;
public static boolean level4test = false;
static boolean drawn = false;
public static boolean key_down;
public static boolean key_up;
public static boolean key_right;
public static boolean key_left;
//private Screen screen;
JButton startButton = new JButton("Start"); //Start
JButton settingsButton = new JButton("Settings"); //Settings
JButton exitButton = new JButton("Exit"); //Exit
public main()
{
setMinimumSize(new Dimension(WIDTH , HEIGHT ));
setMaximumSize(new Dimension(WIDTH , HEIGHT )); // keeps the canvas same size
setPreferredSize(new Dimension(WIDTH, HEIGHT));
frame = new JFrame(name);
if(MAIN_MENU == true && GAME == false){
buttons(frame.getContentPane());
}
frame.setLayout(new BorderLayout());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // ends program on
// close
frame.addKeyListener(new input() );
frame.add(this, BorderLayout.CENTER);
frame.pack(); // keeps size correct
frame.setResizable(false);
frame.setVisible(true);
this.addKeyListener(keypress);
}
public static void main(String[] args)
{
try {
background = ImageIO.read(new File("res\\Background.png"));
pacman = ImageIO.read(new File("res\\pacmansprites.png"));
settingsBackground = ImageIO.read(new File("res\\Background.png"));
level1 = ImageIO.read(new File("res\\level1.png"));
//level2 = ImageIO.read(new File("res\\level2.png"));
point = ImageIO.read(new File("res\\Points for pacman.png"));
} catch (IOException e) {
}
running = true;
new main().start();
}
public void run()
{
long lastTime = System.nanoTime();
double nsPerTick = 1000000000 / 60D;
long lastTimer = System.currentTimeMillis();
double delta = 0;
int frames = 0;
int ticks = 0;
while (running == true) {
long now = System.nanoTime();
delta += (now - lastTime) / nsPerTick;
lastTime = now;
boolean render = false;
while (delta >= 1) {
ticks++;
tick();
delta -= 1;
render = true;
}
try {
Thread.sleep(3); //keep the Frames from going to high
} catch (InterruptedException e) {
e.printStackTrace();
}
if(render == true){
frames++;
render();
}
if (System.currentTimeMillis() - lastTimer >= 1000) {
lastTimer +=1000;
//System.out.println("Frames: " + frames + " Ticks: " + ticks);
frames = 0;
ticks = 0;
}
}
}
public synchronized void start()
{
new Thread(this).start();
run();
}
public synchronized void stop()
{
running = false;
}
public void tick()
{
if (key_up) posY -= speed / 2;
if (key_down) posY += speed;
if (key_left) posX -= speed / 2;
if (key_right) posX += speed;
}
public void render()
{
drawn = false;
if(MAIN_MENU == false && GAME == true)
{
drawMap();
drawChar();
}
else if(MAIN_MENU == false && GAME == false) {
Graphics g = getGraphics();
{
g.drawImage(settingsBackground,0,0,getWidth(),getHeight(),null);
g.dispose();
}
} else {
Graphics g = getGraphics();{
g.drawImage(background,0,0,getWidth(), getHeight(),null);
g.dispose(); //kill it
}
}
}
public void drawMap(){
if(level1test == true){
Graphics g = getGraphics();
{
g.drawImage(level1,0,0,getWidth(),getHeight(),null);
g.dispose();
}
}
if(level2test == true && drawn == false){
Graphics g = getGraphics();
{
g.drawImage(level2,0,0,getWidth(),getHeight(),null);
}
g.dispose();
}
drawn = true;
}
public void drawChar(){
//drawMap();
Graphics g = getGraphics();{
g.drawImage(point,posX,posY,20, 20,null);
g.dispose();
revalidate();
}
}
public void begin() {
if (key_up) System.out.println("up");
if (key_down) System.out.println("down");
if (key_left) System.out.println("left");
if (key_right) System.out.println("right");
}
public void loadMap(){
if(!drawn && level1test){
}else if(!drawn && level2test){
//draw 2nd map here
}else if(!drawn && level3test){
//draw 3rd map here
}
}
public void buttons(Container pane)
{
pane.setLayout(null);
startButton.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent ae) {
MAIN_MENU = false;
GAME = true;
frame.remove(startButton);
frame.remove(settingsButton);
frame.remove(exitButton);
frame.revalidate();
drawMap();
System.out.println("Start Button Clicked");
}
} );
settingsButton.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent ae) {
MAIN_MENU = false;
GAME = false;
frame.remove(startButton);
frame.remove(settingsButton);
frame.remove(exitButton);
frame.revalidate();
frame.repaint();
System.out.println("Settings Button Clicked");
}
} );
exitButton.addActionListener( new ActionListener() {
public void actionPerformed(ActionEvent ae) {
System.out.println("Exit Button Clicked");
System.exit(0);
}
} );
pane.add(startButton);
pane.add(settingsButton);
pane.add(exitButton);
Insets insets = pane.getInsets();
Dimension size = startButton.getPreferredSize();
startButton.setBackground(new Color(0, 0, 0));
startButton.setForeground(Color.CYAN);
startButton.setFocusPainted(false);
startButton.setFont(new Font("Calabri", Font.BOLD, 16));
settingsButton.setBackground(new Color(0, 0, 0));
settingsButton.setForeground(Color.RED);
settingsButton.setFocusPainted(false);
settingsButton.setFont(new Font("Calabri", Font.BOLD, 16));
exitButton.setBackground(new Color(0, 0, 0));
exitButton.setForeground(Color.YELLOW);
exitButton.setFocusPainted(false);
exitButton.setFont(new Font("Calabri", Font.BOLD, 16));
startButton.setBounds((WIDTH - 125) + insets.left, 10 + insets.top,
size.width + 50, size.height + 10);
settingsButton.setBounds((WIDTH - 125) + insets.left, 55 + insets.top,
size.width + 50, size.height + 10);
exitButton.setBounds((WIDTH - 125) + insets.left, 100 + insets.top,
size.width + 50, size.height + 10);
}
}
getGraphics is not how custom painting is done. You should, in your case, override the paint method, and make sure you call super.paint before doing any custom painting.
getGraphics returns the Graphics context last used to paint the component, which could be discarded on the next paint cycle, be null or no longer used by the component
Remember, painting uses the "painters canvas" approach, that is, just like painting in a physical canvas, when you paint into it, you paint over what was previously there, but not erasing it.
Now, if you override paint, you will find that you will have a flickering problem. This is because Canvas
is not double buffered
To solve this, you should consider user a BufferStrategy, which allows you to not only generate multiple buffers to paint to, but also to take control of the paint process itself
Just don't forget to clear each buffer before you paint to it...
Double buffering is the trick that allows you to have flicker-free animation. Basically you have two representations of your canvas, one that's currently being displayed and one that you can draw on. If you're finished with drawing, you copy the draw-canvas over the display-canvas. Depending on system and hardware there are more elegant ways where you can just tell the hardware to switch canvases (page flipping).
Without double buffering or a similar techniques, it is almost impossible to have flicker-free animation.
With double buffering you can afford to draw the background and then the foreground sprites. It is possibly more efficient to draw only those parts of the background that have been destroyed by the foreground sprites (there are various techniques for that as well, including of taking a snapshot image of the affected areas before you paint the sprites).
You can find a simple example for Java double buffering here. Java's BufferStrategy is a more complex solution that can use hardware features for page flipping.
I think the problem is that you only draw onto the image background, never erasing the old drawing from your image. You will need to clear the area and then start drawing in order to get your desired results.
I have never attempted to make a game but when I do simple animations I usually will do them on a JFrame or JPanel. With a JFrame you can Override the paint() method and with a JPanel, the paintComponent() method. It helps to keep everything that I'm drawing centralized, which makes it much easier for me to modify my code. When you call the respective super method in your overridden method, it will start you off with a clean slate, meaning you will have to paint the (image) background and your characters all over again. Calling the super method is also necessary to paint that component's children if you decide to add anything onto the JFrame/JPanel.
If you chose to use one of the above then I would recommend a JPanel due to it offering double buffering, which will help make your animations look smooth/fluid. Also, do not forget to call repaint();.
Here is a quick example, which can be used to duplicate your issue if you comment out super.paintComponent(g);.
*Note that I am extending and using a JPanel for this example.
Code:
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Trial extends JPanel{
public static void main(String[] args)
{
new Trial();
}
int x = 5; // will represent the x axis position for our crude animation.
javax.swing.Timer timer = new javax.swing.Timer( 500, new ActionListener(){
// Timer used to control the animation and
// the listener is used to update x position and tell it to paint every .5 seconds.
#Override
public void actionPerformed(ActionEvent e) {
x += 5;
if ( x > 250)
timer.stop();
repaint(); // will call the paintComponent method.
}
});
Trial()
{
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.add(this);
frame.setSize(300, 200);
frame.setVisible(true);
timer.start();
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g); // calling the super paintComponent method to paint children. can comment it
// out to duplicate your error, which happens when the frame isn't "refreshed".
Graphics2D g2d = (Graphics2D) g.create(); // creating a copy of the graphics object. // I do this to not alter the original
// Good practice and also Graphics2D
// offers a bit more to work with.
g2d.drawString("Graphics", x, 60); // painting a string.
g2d.drawRect(x, 80, 10, 10); // painting a rectangle.
}
}
Edit:
If you have a bunch of stuff to do and don't want to add it all into your paintComponent(); method you could create a method for various things and call them from inside your Overriden paint method, you could also pass them your graphics object. It will help you keep things relatively simple.
How do you write every every repaint call (direct or not) by a JPanel-type (i.e. a custom class which extends/inherits from JPanel) to a BufferedImage ?
Doing this sort of thing inside the custom class' paintComponent does not work:
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D G2 = (Graphics2D) g;
// ... draw objects
BufferedImage imageBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
G2 = imageBuffer.createGraphics();
// Which doesn't work, because with that method it seems you would
// need to call paint() on Graphics2D reference here.
// And to do so would then throw an Illegal Exception.
}
The resulting BufferedImage is the correct size of the JPanel-type class which calls paintComponent, but the image is black (i.e. empty) - which is entirely logical because createGraphics() alone does nothing.
I know of Rob Camick's ScreenImage code - but that seems intended for a single screenshot at initialisation of the program.
What leaves me confused is that what paintComponent does must be held in memory before being displayed on-screen... So is there a way of grabbing that every time and saving it into a BufferedImage ?
What about this approach? The difference from you posted code is that you need to do all your custom drawing onto your BufferedImage. Then you draw just one thing onto the component: the BufferedImage.
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
BufferedImage imageBuffer = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
Graphics imageG2 = imageBuffer.createGraphics();
// do custom painting onto imageG2...
// save imageBuffer
g2.drawImage(imageBuffer, 0, 0, this); // draw onto component
}
The description and some details may not be entirely clear. But it basically sounds like you wanted to create a component that can be used like a normal JPanel, but everything that is painted in the paintComponent method should also be saved as a BufferedImage.
There are different solutions for this. You could make dedicated calls, like
myComponent.paintComponent(bufferedImageGraphics);
and process the image accordingly, as proposed in one answer. Another answer suggested creating a custom paintComponent method that does the image handling.
However, I'd like to propose the following solution: You could create a ImageSavingComponent class that extends JPanel. In this class, you don't override the paintComponent method, but the paint method. The paint method does two things:
It calls super.paint(bufferedImageGraphics), painting into an image
It calls super.paint(componentGraphics), painting to the screen
This way, you may simply extend this class, and implement the paintComponent method like you would do for any JPanel. The image handling can be done completely transparently.
In the example below, I added a Consumer interface that receives the images and handles them appropriately. If the Consumer is null, then no images will be produced. For the test, I created an implementation of this interface that simply stores the images in files every 200ms. (This interface may be replaced with the Java 8 Consumer interface, if desired)
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.FileOutputStream;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JToggleButton;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class ImageSavingComponentTest {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
createAndShowGUI();
}
});
}
private static void createAndShowGUI() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final ImageSavingComponent c = new ExampleImageSavingComponent();
f.getContentPane().add(c);
final Timer timer = new Timer(50, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
c.repaint();
}
});
timer.start();
final Consumer<? super BufferedImage> consumer = new ImageSaver();
final JToggleButton b = new JToggleButton("Capture");
b.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (b.isSelected()) {
c.setConsumer(consumer);
} else {
c.setConsumer(null);
}
}
});
c.add(b);
f.setSize(300, 300);
f.setLocationRelativeTo(null);
f.setVisible(true);
}
}
class ImageSaver implements Consumer<BufferedImage>
{
private int counter = 0;
private long previousFrameMillis = -1;
private long intervalMillis = 200;
#Override
public void accept(BufferedImage t) {
if (System.currentTimeMillis() < previousFrameMillis + intervalMillis)
{
return;
}
previousFrameMillis = System.currentTimeMillis();
String fileName = String.format("image%04d.png", counter);
counter++;
FileOutputStream fos = null;
try {
fos = new FileOutputStream(fileName);
ImageIO.write(t, "png", fos);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
class ExampleImageSavingComponent extends ImageSavingComponent {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
double time = (System.currentTimeMillis() % 2000) / 2000.0;
double ca = Math.cos(time * Math.PI * 2);
double sa = Math.sin(time * Math.PI * 2);
g.setColor(Color.RED);
double d = 50;
int x = getWidth() / 2 + (int) (ca * d);
int y = getHeight() / 2 + (int) (sa * d);
int r = 10;
g.fillOval(x - r, y - r, r + r, r + r);
}
}
interface Consumer<T> {
void accept(T t);
}
class ImageSavingComponent extends JPanel {
private Consumer<? super BufferedImage> consumer;
void setConsumer(Consumer<? super BufferedImage> consumer) {
this.consumer = consumer;
}
#Override
public final void paint(Graphics g) {
if (consumer != null && getWidth() > 0 && getHeight() > 0) {
BufferedImage image = new BufferedImage(getWidth(), getWidth(),
BufferedImage.TYPE_INT_ARGB);
Graphics2D imageGraphics = image.createGraphics();
super.paint(imageGraphics);
imageGraphics.dispose();
consumer.accept(image);
}
super.paint(g);
}
}
You must create the BufferedImage outside the paint function of your component, and then call this paint function with the BufferedImage graphics as parameter.
BufferedImage imageBuffer = new BufferedImage(comp.getWidth(), cmp.getHeight(), BufferedImage.TYPE_INT_RGB);
cmp.paint(imageBuffer.getGraphics());
I'm trying to make a fairly basic 3D game in preparation for what's to come when I enter Ludum Dare 23, an annual game competition.
I'm using Java and I'm using Swing and AWT. (I'm using JFrame to create my window (obviously? idk))
The problem I'm having is occurring when I'm attempting to draw pixels on the screen. I get a number of exceptions regarding BufferStrategy and what's apparently a Thread issue.
The following is what the console box in Eclipse is throwing me.
Exception in thread "Thread-2" java.lang.IllegalStateException: Component must have a valid peer
at java.awt.Component$FlipBufferStrategy.createBuffers(Component.java:3982)
at java.awt.Component$FlipBufferStrategy.<init>(Component.java:3956)
at java.awt.Component$FlipSubRegionBufferStrategy.<init>(Component.java:4479)
at java.awt.Component.createBufferStrategy(Component.java:3833)
at java.awt.Canvas.createBufferStrategy(Canvas.java:194)
at java.awt.Component.createBufferStrategy(Component.java:3756)
at java.awt.Canvas.createBufferStrategy(Canvas.java:169)
at com.ottdev.tale.TaleOfDwarvesComponent.render(TaleOfDwarvesComponent.java:66)
at com.ottdev.tale.TaleOfDwarvesComponent.run(TaleOfDwarvesComponent.java:55)
at java.lang.Thread.run(Thread.java:722)
Any ideas on how I could solve this issue? It's doing my head in and is preventing me from advancing. All help is highly appreciated!
PS: I can put up my source code if need be, but for now, I'd rather know how to fix it so I can know what to do if I encounter this situation in the future, but with different code.
EDIT: My main class -
TaleOfDwarvesComponent:
package com.ottdev.tale;
import java.awt.*;
import java.awt.image.*;
import javax.swing.*;
import com.ottdev.tale.gui.*;
public class TaleOfDwarvesComponent extends Canvas implements Runnable {
public static final int WIDTH = 800;
public static final int HEIGHT = 600;
public static final int SCALE = 2;
public static final String TITLE = "Tale Of Dwarves!";
private boolean running = false;
private BufferedImage img;
public int[] pixels;
private Thread thread;
private Game game;
private Screen screen;
public TaleOfDwarvesComponent() {
game = new Game();
screen = new Screen(WIDTH, HEIGHT);
img = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
pixels = ((DataBufferInt) img.getRaster().getDataBuffer()).getData();
}
public void start() {
if (running)
return;
running = true;
new Thread(this).start();
}
public void stop() {
if (!running)
return;
running = false;
try{
thread.join();
}catch(InterruptedException e){
e.printStackTrace();
}
}
public void run() {
while(running){
render();
}
}
public void tick(){
game.tick();
}
public void render(){
BufferStrategy bs = getBufferStrategy();
if(bs == null){
createBufferStrategy(3);
return;
}
screen.render(game);
for (int i = 0; i < WIDTH * HEIGHT; i++){
pixels[i] = screen.pixels[i];
}
Graphics g = bs.getDrawGraphics();
g.fillRect(0, 0, getWidth(), getHeight());
g.drawImage(img, 0, 0, WIDTH * SCALE, HEIGHT * SCALE, null);
g.dispose();
bs.show();
}
public static void main(String[] args) {
TaleOfDwarvesComponent game = new TaleOfDwarvesComponent();
JFrame frame = new JFrame();
frame.setSize(WIDTH, HEIGHT);
frame.setLocationRelativeTo(null);
frame.setTitle(TITLE);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
game.start();
}
}
I have a Bitmap class and a Screen class, but for now, I'll put this up in hope that the answer to my troubles can be obtained from this.
If you look at the javadocs http://docs.oracle.com/javase/7/docs/api/java/awt/Canvas.html#createBufferStrategy(int)
I believe it specifically addresses your problem, that is, that you are calling createBufferStrategy before the Canvas is displayed on the screen. So to solve your problem, you need to either add a ComponentListener to your Canvas and call createBufferStrategy on componentShown, or call this in your paint method