How to fix bad Double-Buffering [closed] - java

It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 9 years ago.
I tried following a double buffering tutorial, and I really don't know what I did wrong. It works before then before I did the tutorial, but there is still and occasional flicker here and there. I have two files Game and gameLoop
Game:
import java.awt.Graphics;
public class Game extends gameLoop
{
public void init()
{
setSize(854,480);
Thread th = new Thread(this);
th.start();
offscreen = createImage(854,480);
d = offscreen.getGraphics();
}
public void paint(Graphics g)
{
d.clearRect(0, 0, 854, 480);
d.drawImage(disk, x, y, this);
g.drawImage(offscreen , 0, 0, this);
}
public void Update(Graphics gfx)
{
paint(gfx);
}
}
gameLoop
import java.applet.Applet;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.PointerInfo;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
public class gameLoop extends Applet implements Runnable, MouseListener, MouseMotionListener
{
public int x, y, counter, mouseX, mouseY;
public Image offscreen;
public Graphics d;
public boolean up, down, left, right, pressed;
public BufferedImage disk1, disk2, disk3, disk4, disk;
public int ballSpeedX = -6;
public int ballSpeedY = -3;
public void run()
{
x = 400;
y = 200;
try {
disk1 = ImageIO.read(new File("disk1.png"));
disk2 = ImageIO.read(new File("disk2.png"));
disk3 = ImageIO.read(new File("disk3.png"));
disk4 = ImageIO.read(new File("disk4.png"));
} catch (IOException e1) {
e1.printStackTrace();
}
while(true)
{
if(x >= (854 - 150))
{
ballSpeedX = ballSpeedX * -1;
}
if(y >= (480 - 140))
{
ballSpeedY = ballSpeedY * -1;
}
if(y < (0 - 10))
{
ballSpeedY = ballSpeedY * -1;
}
if(x < (0- 10))
{
ballSpeedX = ballSpeedX * -1;
}
x = x + ballSpeedX;
y = y + ballSpeedY;
counter ++;
if(counter >= 4)
counter = 0;
if(counter == 0)
disk = disk1;
if(counter == 1)
disk = disk2;
if(counter == 2)
disk = disk3;
if(counter == 3)
disk = disk4;
System.out.println(counter);
repaint();
try {
Thread.sleep(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void mouseClicked(MouseEvent e) {}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
public void mouseMoved(MouseEvent m) {}
public void mousePressed(MouseEvent m)
{
}
public void mouseReleased(MouseEvent m)
{
pressed = false;
}
public void mouseDragged(MouseEvent e) {
PointerInfo a = MouseInfo.getPointerInfo();
Point b = a.getLocation();
mouseX = (int)b.getX();
mouseY = (int)b.getY();
ballSpeedX = mouseX;
ballSpeedY = mouseY;
}
}

public void Update(Graphics gfx)
{
paint(gfx);
}
Should be lower case, as you're trying to override the update(Graphics g) method in the Applet.
So it should be
#Override
public void update(Graphics gfx)
{
paint(gfx);
}
As for changing the background, a background is simply a big rectangle that covers the screen and makes it a certain color. In your paint, you're doing clearRect, which clears the screen. Instead just switch that to fillRect after setting the color.
It might look something like
public void paint(Graphics g)
{
//setColor to whatever you want
//fillRect to cover the screen
}
When you're doing this though, one thing you have to remember is to not confuse your two graphics objects. As a concept, double buffering works by drawing to memory first (drawing it on an offscreen image) and then to the screen. You want to always draw onto this offscreen image, as it is much faster (and we lose the flicker).
So make sure you're doing imageGraphicsObject.setColor not screenGraphicsObject.setColor or imageGraphicsObject.fillRectnotscreenGraphicsObject.fillRect`. Otherwise you're no longer double-buffering.

There are a number of different types "double buffers". The basic is a simple off screen image that you update and this gets painted to the screen instead. If done right, painting the image is often faster then drawing graphics to the screen.
Another type is page flipping. That is, you have an active buffer which is always rendered to the screen and a off screen/scratch buffer which is what you actually render to. You then flip these buffers when you are ready to render the updates to the screen (this is closer to how film animation works).
The following example is a REALLY basic example of page flipping.
import java.applet.Applet;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Panel;
import java.awt.image.BufferedImage;
import static testdoublebuffer.TestDoubleBuffer.UPDATE;
public class AppletDoubleBuffer extends Applet {
private BufferedPane pane;
#Override
public void init() {
pane = new BufferedPane();
setLayout(new BorderLayout());
add(pane);
}
#Override
public void start() {
pane.start();
}
#Override
public void stop() {
pane.stop();
}
public class BufferedPane extends Panel {
private BufferedImage activeBuffer;
private BufferedImage scratch;
private boolean running = false;
public BufferedPane() {
}
public void start() {
if (!running) {
running = true;
Thread thread = new Thread(new MainLoop());
thread.setDaemon(true);
thread.start();
}
}
public void stop() {
running = false;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
public void invalidate() {
synchronized (UPDATE) {
activeBuffer = null;
scratch = null;
}
super.invalidate();
}
#Override
public void update(Graphics g) {
if (activeBuffer != null) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.drawImage(activeBuffer, 0, 0, this);
g2d.dispose();
}
}
public class MainLoop implements Runnable {
private int delay = 1000 / 25;
private int x = 0;
private int velocity = 5;
private int size = 10;
public void update() {
x += velocity;
if (x + size >= getWidth()) {
x = getWidth() - size;
velocity *= -1;
} else if (x <= 0) {
x = 0;
velocity *= -1;
}
if (getWidth() > 0 && getHeight() > 0) {
synchronized (UPDATE) {
if (scratch == null) {
scratch = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_ARGB);
}
Graphics2D g2d = scratch.createGraphics();
int y = (getHeight() - size) / 2;
g2d.setBackground(Color.BLUE);
g2d.clearRect(0, 0, getWidth(), getHeight());
g2d.setColor(Color.RED);
g2d.fillOval(x, y, size, size);
g2d.dispose();
// Flip the buffers...
BufferedImage tmp = activeBuffer;
activeBuffer = scratch;
scratch = tmp;
}
}
}
#Override
public void run() {
while (running) {
long startTime = System.currentTimeMillis();
update();
repaint();
long duration = System.currentTimeMillis() - startTime;
if (duration < delay) {
try {
Thread.sleep(delay);
} catch (InterruptedException ex) {
}
}
}
}
}
}
}
I personally, would just skip using AWT and move to Swing which provides better/built in functionality for this type of thing.

Related

How do I make a top down view with the ability to rotate with built in java graphics?

I'm trying to make a racing game with the top down view on a static player in the middle of the screen, so instead of moving the player through the map, the map would move around the player. Since it's a racing game, I wanted it to also be somewhat similar to a car, but I've been having trouble with rotating the map around the player and having that work with translations.
I've tried keeping track of the center by adding or subtracting from it, which is what I did for the translations, but it doesn't work with the rotate method. The rotate function wouldn't rotate about the player and instead would rotate the player around some other point, and the translations would snap to a different location from the rotations. I'm sure my approach is flawed, and I have read about layers and such, but I'm not sure what I can do with them or how to use them. Also, any recommendations as to how to use java graphics in general would be greatly appreciated!
This is what I have in my main:
import javax.swing.JFrame;
import java.awt.BorderLayout;
public class game
{
public static void main(String []args)
{
JFrame frame = new JFrame();
final int FRAME_WIDTH = 1000;
final int FRAME_HEIGHT = 600;
frame.setSize(FRAME_WIDTH, FRAME_HEIGHT);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final Map b = new Map();
frame.add(b,BorderLayout.CENTER);
frame.setVisible(true);
b.startAnimation();
}
}
And this is the class that handles all the graphics
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JComponent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
public class Map extends JComponent implements Runnable, KeyListener
{
private int speed = 5;
private int xcenter = 500; // starts on player
private int ycenter = 300;
private double angle = 0.0;
private int[] xcords = {xcenter+10, xcenter, xcenter+20};
private int[] ycords = {ycenter-10, ycenter+20, ycenter+20};
private boolean moveNorth = false;
private boolean moveEast = false;
private boolean moveSouth = false;
private boolean moveWest = false;
public Map()
{
addKeyListener(this);
setFocusable(true);
setFocusTraversalKeysEnabled(false);
}
public void startAnimation()
{
Thread t = new Thread(this);
t.start();
}
public void paintComponent(Graphics g)
{
g.fillPolygon(xcords, ycords, 3);
// move screen
if(moveNorth)
{
ycenter += speed;
g.translate(xcenter, ycenter);
}
else if(moveEast)
{
angle += ((1 * Math.PI/180) % (2 * Math.PI));
((Graphics2D) g).rotate(angle, 0, 0);
}
else if(moveSouth)
{
System.out.println(xcenter + ", " + ycenter);
ycenter -= speed;
((Graphics2D) g).rotate(angle, 0, 0);
g.translate(xcenter, ycenter);
}
else if(moveWest)
{
angle -= Math.toRadians(1) % (2 * Math.PI);
((Graphics2D) g).rotate(angle, 0, 0);
}
for(int i = -10; i < 21; i++)
{
g.drawLine(i * 50, -1000, i * 50, 1000);
g.drawLine(-1000, i * 50, 1000, i * 50);
}
g.drawOval(0, 0, 35, 35);
}
public void run()
{
while (true)
{
try
{
if(moveNorth || moveEast || moveSouth || moveWest)
{
repaint();
}
Thread.sleep(10);
}
catch (InterruptedException e)
{
}
}
}
public void keyPressed(KeyEvent e)
{
if(e.getExtendedKeyCode() == 68) // d
{
moveEast = true;
}
else if(e.getExtendedKeyCode() == 87) // w
{
moveNorth = true;
}
else if(e.getExtendedKeyCode() == 65) // a
{
moveWest = true;
}
else if(e.getExtendedKeyCode() == 83) // s
{
moveSouth = true;
}
}
public void keyReleased(KeyEvent e)
{
moveNorth = false;
moveEast = false;
moveSouth = false;
moveWest = false;
}
public void keyTyped(KeyEvent e)
{
}
}
You have to keep in mind that transformations are compounding, so if you rotate the Graphics context by 45 degrees, everything painted after it will be rotated 45 degrees (around the point of rotation), if you rotate it again by 45 degrees, everything painted after it will be rotated a total of 90 degrees.
If you want to paint additional content after a transformation, then you either need to undo the transformation, or, preferably, take a snapshot of the Graphics context and dispose of it (the snapshot) when you're done.
You also need to beware of the point of rotation, Graphics2D#rotate(double) will rotate the Graphics around the point of origin (ie 0x0), which may not be desirable. You can change this by either changing the origin point (ie translate) or using Graphics2D#rotate(double, double, double), which allows you to define the point of rotation.
For example...
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.awt.event.KeyEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
public class Main {
public static void main(String[] args) {
new Main();
}
public Main() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (IOException ex) {
Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
}
public class TestPane extends JPanel {
enum Direction {
LEFT, RIGHT;
}
protected enum InputAction {
PRESSED_LEFT, PRESSED_RIGHT, RELEASED_LEFT, RELEASED_RIGHT
}
private BufferedImage car;
private BufferedImage road;
private Set<Direction> directions = new TreeSet<>();
private double directionOfRotation = 0;
public TestPane() throws IOException {
car = ImageIO.read(getClass().getResource("/images/Car.png"));
road = ImageIO.read(getClass().getResource("/images/Road.png"));
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false), InputAction.PRESSED_LEFT);
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, true), InputAction.RELEASED_LEFT);
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false), InputAction.PRESSED_RIGHT);
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, true), InputAction.RELEASED_RIGHT);
am.put(InputAction.PRESSED_LEFT, new DirectionAction(Direction.LEFT, true));
am.put(InputAction.RELEASED_LEFT, new DirectionAction(Direction.LEFT, false));
am.put(InputAction.PRESSED_RIGHT, new DirectionAction(Direction.RIGHT, true));
am.put(InputAction.RELEASED_RIGHT, new DirectionAction(Direction.RIGHT, false));
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (directions.contains(Direction.RIGHT)) {
directionOfRotation += 1;
} else if (directions.contains(Direction.LEFT)) {
directionOfRotation -= 1;
}
// No doughnuts for you :P
if (directionOfRotation > 180) {
directionOfRotation = 180;
} else if (directionOfRotation < -180) {
directionOfRotation = -180;
}
repaint();
}
});
timer.start();
}
protected void setDirectionActive(Direction direction, boolean active) {
if (active) {
directions.add(direction);
} else {
directions.remove(direction);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(213, 216);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
drawRoadSurface(g2d);
drawCar(g2d);
g2d.dispose();
}
protected void drawCar(Graphics2D g2d) {
g2d = (Graphics2D) g2d.create();
int x = (getWidth() - car.getWidth()) / 2;
int y = (getHeight() - car.getHeight()) / 2;
g2d.drawImage(car, x, y, this);
g2d.dispose();
}
protected void drawRoadSurface(Graphics2D g2d) {
g2d = (Graphics2D) g2d.create();
// This sets the point of rotation at the center of the window
int midX = getWidth() / 2;
int midY = getHeight() / 2;
g2d.rotate(Math.toRadians(directionOfRotation), midX, midY);
// We then need to offset the top/left corner so that what
// we want draw appears to be in the center of the window,
// and thus will be rotated around it's center
int x = midX - (road.getWidth() / 2);
int y = midY - (road.getHeight() / 2);
g2d.drawImage(road, x, y, this);
g2d.dispose();
}
protected class DirectionAction extends AbstractAction {
private Direction direction;
private boolean active;
public DirectionAction(Direction direction, boolean active) {
this.direction = direction;
this.active = active;
}
#Override
public void actionPerformed(ActionEvent e) {
setDirectionActive(direction, active);
}
}
}
}

Why isn't the run method executed?

I'm trying to create a square that can move by pressing keys. When I Compiled & Ran the code it wouldn't move. So I began debugging (as well as I'm capable of). The problem seems to be that the run() function isn't being called. Why is this ? My understanding was that when using the interface Runnable, the run method is called automatically. I posted all the code in action.
Why isn't run() being called automatically and how can I change my program so it will call ?
Game.java
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
public class Game extends JPanel implements Runnable{
private static final int WIDTH = 800, HEIGHT = WIDTH / 12 * 9; //Widescreen
private Thread game_thread;
private boolean running = false;
public int x_speed = 0, y_speed = 0;
public Square square;
public Game(){
game_thread = new Thread("GameThread");
square = new Square(this);
addKeyListener(new KeyListener(){
#Override
public void keyPressed(KeyEvent e) {
if(e.getKeyCode() == e.VK_A){
x_speed = -1;
}
if(e.getKeyCode() == e.VK_D){
x_speed = 1;
}
if(e.getKeyCode() == e.VK_S){
y_speed = -1;
}
if(e.getKeyCode() == e.VK_W){
y_speed = 1;
}
}
#Override
public void keyReleased(KeyEvent e) {
}
#Override
public void keyTyped(KeyEvent e) {
}
});
}
public void start(){
System.out.println("Started");
game_thread.start();
running = true;
System.out.println(running);
}
public void stop(){
try{
running = false;
game_thread.join();
}catch(InterruptedException e){
e.printStackTrace();
}
}
public void paint(Graphics g){
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setColor(Color.BLACK);
g2d.fillRect(0, 0, WIDTH, HEIGHT);
square.render(g2d);
}
public void update(){
square.move();
System.out.println(x_speed + ", " + y_speed);
}
public void run(){
System.out.println("run method started");
while(running){
System.out.println("Running");
//Update screen info
update();
//Re-render
repaint();
try{
game_thread.sleep(10);
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
public static void main(String args[]){
JFrame frame = new JFrame("Moving Thangs");
Game game = new Game();
frame.setSize(game.WIDTH, game.HEIGHT);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(frame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.add(game);
frame.setVisible(true);
game.start();
}
}
Square.java
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
public class Square {
public static final int s_WIDTH = 80, s_HEIGHT = s_WIDTH;
public int x, y;
private Game game;
public Square(Game game){
x = 50;
y = 50;
this.game = game;
}
public void move(){
if(x >= 0 && x <= game.getWidth() - s_WIDTH){
x += game.x_speed;
}
if(y >= 0 && y <= game.getHeight() - s_HEIGHT){
y += game.y_speed;
}
}
public void render(Graphics2D g2d){
g2d.setColor(Color.ORANGE);
g2d.fillRect(x, y, s_WIDTH, s_HEIGHT);
}
}
When you create the thread using new Thread("GameThread") you don't pass this as a runnable to the thread. You need to pass it as the first argument in the constructor like new Thread(this, "GameThread") and then everything should work.

The repaint method stops working for short delays

I'm trying to create a simple panel where a 2-dimensional ball is bouncing up and down. I can't get it to work because for some reason I can't call the repaint method more than once a second. The design is basically that there is an object that can be given "an impulse" with the method move(). Everytime the evaluatePosition method is called, the current position will be calculated through the time that has passed, the velocity and the acceleration. The code for the panel is:
public class Display extends JPanel {
private MovableObject object = new MovableObject(new Ellipse2D.Double(5,5,50,50));
private static final int DELAY = 1000;
public Display(){
object.move(50,50);
Timer timer = new Timer(DELAY, new ActionListener(){
#Override
public void actionPerformed(ActionEvent e){
object.evaluatePosition();
repaint();
}
});
timer.start();
}
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g.drawOval((int)object.getPosition().getX(), (int)object.getPosition.getY()
(int)object.getShape().getWidth(), object.getShape().getHeight());
}
This code works for DELAY=1000 but not for DELAY=100 or DELAY=10 and so on. I read some example code here on SO but they all seem to me like what I already did. So why is my code not working?
EDIT (2016-01-30):
Since it really seems to be a performance issue, here's the code for the MovableObject (I just thought it would be irrelevant and you will probably see why):
public class MovableObject {
// I would really like to use Shape instead of Ellipse2D so that
// objects of any shape can be created
private Ellipse2D.Double shape;
private Point position;
// Vector is my own class. I want to have some easy vector addition and
// multiplication and magnitude methods
private Vector velocity = new Vector(0, 0);
private Vector acceleration = new Vector(0, 0);
private Date lastEvaluation = new Date();
public MovableObject(Ellipse2D.Double objectShape){
shape = objectShape;
}
public void evaluatePosition(){
Date currentTime = new Date();
long deltaTInS = (currentTime.getTime()-lastEvaluation.getTime())/1000;
// s = s_0 + v*t + 0.5*a*t^2
position = new Point((int)position.getX()+ (int)(velocity.getX()*deltaTInS) + (int)(0.5*acceleration.getX()*deltaTInS*deltaTInS),
(int)position.getY()+ (int)(velocity.getY()*deltaTInS) + (int)(0.5*acceleration.getY()*deltaTInS*deltaTInS));
lastEvaluation = currentTime;
}
}
public void move(Vector vector){
velocity = velocity.add(vector);
evaluatePosition();
}
public Point getPosition(){
return position;
}
public Ellipse2D.Double getShape(){
return shape;
}
My move method does not change position but velocity. Please notice that I just changed the shape Object from Shape to Ellipse2D for testing if my code has a performance issue because of the additional code. So if you think this is more complex than it needs to be: I actually want to add some complexity so that the MovableObject can have the shape of any subclass of shape. I've seen a lot of code that seemed more complex to me and rendered fast. So I'd like to know what's wrong with this (besides the fact that it's a bit too complex for just rendering an ellipse).
Okay, so this is a simple example, based on the out-of-context code snippet you left which doesn't seem to have any problems. It has variable speed controlled by a simple slider...
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.PathIterator;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JSlider;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
public class Display extends JPanel {
public static void main(String[] args) {
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 Display());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
private MovableObject object = new MovableObject(new Ellipse2D.Double(5, 5, 50, 50));
private int delay = 40;
private Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
object.evaluatePosition(getSize());
repaint();
}
});
private JSlider slider = new JSlider(5, 1000);
public Display() {
object.move(50, 50);
slider = new JSlider(5, 1000);
slider.setSnapToTicks(true);
slider.setMajorTickSpacing(10);
slider.setMinorTickSpacing(5);
setLayout(new BorderLayout());
add(slider, BorderLayout.SOUTH);
// This is simply designed to put an artificate delay between the
// change listener and the time the update takes place, the intention
// is to stop it from pausing the "main" timer...
Timer delay = new Timer(250, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (timer != null) {
timer.stop();
}
timer.setDelay(slider.getValue());
timer.start();
}
});
slider.addChangeListener(new ChangeListener() {
#Override
public void stateChanged(ChangeEvent e) {
delay.restart();
repaint();
}
});
slider.setValue(40);
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.draw(object.getTranslatedShape());
FontMetrics fm = g2.getFontMetrics();
String text = Integer.toString(slider.getValue());
g2.drawString(text, 0, fm.getAscent());
g2.dispose();
}
public class MovableObject {
private Shape shape;
private Point location;
private int xDelta, yDelta;
public MovableObject(Shape shape) {
this.shape = shape;
location = shape.getBounds().getLocation();
Random rnd = new Random();
xDelta = rnd.nextInt(8);
yDelta = rnd.nextInt(8);
if (rnd.nextBoolean()) {
xDelta *= -1;
}
if (rnd.nextBoolean()) {
yDelta *= -1;
}
}
public void move(int x, int y) {
location.setLocation(x, y);
}
public void evaluatePosition(Dimension bounds) {
int x = location.x + xDelta;
int y = location.y + yDelta;
if (x < 0) {
x = 0;
xDelta *= -1;
} else if (x + shape.getBounds().width > bounds.width) {
x = bounds.width - shape.getBounds().width;
xDelta *= -1;
}
if (y < 0) {
y = 0;
yDelta *= -1;
} else if (y + shape.getBounds().height > bounds.height) {
y = bounds.height - shape.getBounds().height;
yDelta *= -1;
}
location.setLocation(x, y);
}
public Shape getTranslatedShape() {
PathIterator pi = shape.getPathIterator(AffineTransform.getTranslateInstance(location.x, location.y));
GeneralPath path = new GeneralPath();
path.append(pi, true);
return path;
}
}
}
You could also have a look at
Swing animation running extremely slow
Rotating multiple images causing flickering. Java Graphics2D
Java Bouncing Ball
for some more examples...

Swing 2D game low performance

I am making a clone of Flappy Bird. I was doing just fine performance-wise: 60 fps. This was when it had 1 pillar/obstacle only. As soon as I added 3 of them my fps dropped to 30 and below. Then game is unplayable now. I get that this has something to do with doing repaint() all the time.
Here is the code:
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
/**
* Created by Lazar on 25/05/15.
*/
public class Environment extends JComponent implements ActionListener {
public static final Dimension dimension = new Dimension(800,600);
BufferedImage img;
BufferedImage ptica1;
BufferedImage ptica2;
double skokbrojac = 0;
int brzina = 4; // speed // MUST Background % brzina = 0
int dx;
int dx2;
int pad = 0; //drop
Timer timer;
boolean parno;
boolean skok = false;
//Stubovi // Pillars
Stub stub1 = new Stub();
Stub stub2 = new Stub();
Stub stub3 = new Stub();
ArrayList<Stub>stubovi = new ArrayList<Stub>();
int razmakStub; // Space between pillars
public Environment() {
setPreferredSize(dimension);
img = Util.openImage("pozadina.png");
ptica1 = Util.openImage("ptica1.png");
ptica2 = Util.openImage("ptica2.png");
stubovi.add(stub1);
stubovi.add(stub2);
stubovi.add(stub3);
dx = img.getWidth()/2;
timer = new Timer(1000/60,this);
timer.start();
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
super.mousePressed(e);
skok = true; // start jump
skokbrojac = 0; //jump frame counter
}
});
}
protected void paintComponent(Graphics g){
Graphics2D g2d = (Graphics2D)g;
//g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
//g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
if(dx == img.getWidth()){ //image horizontal scroll
dx2 = 0;
}
if(dx2 == img.getWidth()/2){ //image horizontal scroll
dx = dimension.width;
}
g2d.drawImage(img,getWidth() - dx, 0, null); //draw background
if(dx >= img.getWidth()){
g2d.drawImage(img,getWidth() - dx2, 0, null);
}
if(parno){
g2d.drawImage(ptica1,dimension.width/2, 290 + pad, null); //draw bird
}
else{
g2d.drawImage(ptica2,dimension.width/2, 290 + pad, null); //draw bird
}
stub1.postoji = true; //pillar1 exists?
if(razmakStub > 240){
stub2.postoji = true;
}
if(razmakStub > 480){ //pillar1 exists?
stub3.postoji = true;
}
for(Stub i : stubovi){ //draw pillars if they exist
if(i.postoji)
i.crtaj(g2d);
}
}
#Override
public void actionPerformed(ActionEvent e) {
dx = dx + brzina;
dx2 = dx2 + brzina;
if(skokbrojac > 5) // jump frame lenght
skok = false;
if(skok){
pad -= 15; // jump height
}
else{
pad += 8; //rate of the fall
}
skokbrojac++;
parno ^= true; // for different bird images
if(290 + pad >= 536 || 290 + pad<= 3) //border hit detect
timer.stop();
razmakStub += brzina;
for(Stub i : stubovi){ //reset pillars and make them move
if(i.postoji){
if(i.getDx() < -50){
i.setDx(800);
i.randomDy();
}
i.setDx(i.getDx() - brzina);
}
}
repaint();
}
}
Complete project source
Also bear in mind this is really unpolished version so the code is ugly. I am looking for a solution to boost performance.
Main Class:
import javax.swing.*;
/**
* Created by Lazar on 25/05/15.
*/
public class Main {
public static void main(String[] args){
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Frame(new Environment());
}
});
}
}
Frame class:
import javax.swing.*;
/**
* Created by Lazar on 25/05/15.
*/
public class Frame extends JFrame{
public Frame(JComponent content){
setContentPane(content);
setTitle("Flappy");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(getPreferredSize());
setResizable(false);
setVisible(true);
setLocationRelativeTo(null);
}
}
Stub/Pillar class:
import java.awt.*;
import java.awt.image.BufferedImage;
import java.util.Random;
/**
* Created by Lazar on 26/05/15.
*/
public class Stub {
BufferedImage dole;
BufferedImage gore;
Random r = new Random();
int dx = 700;
int dy = r.nextInt(250) + 250;
boolean postoji = false;
public void crtaj(Graphics2D g2d){
dole = Util.openImage("stub_dole.png");
gore = Util.openImage("stub_gore.png");
g2d.drawImage(dole, dx, dy, null);
g2d.drawImage(gore, dx, -(560-dy), null);
}
public void setDx(int dx) {
this.dx = dx;
}
public void randomDy(){
this.dy = r.nextInt(250) + 250;
}
public int getDx() {
return dx;
}
}
Ptica/Brid class:
import java.awt.Graphics;
import java.awt.image.BufferedImage;
/**
* Created by Lazar on 26/05/15.
*/
public class Ptica {
BufferedImage ptica1;
BufferedImage ptica2;
boolean ptica;
boolean skok = false;
int pad = 0;
double skokBrojac = 0;
public Ptica(){
ptica1 = Util.openImage("/slike/ptica1.png");
ptica2 = Util.openImage("/slike/ptica2.png");
}
public void crtajPticu(Graphics g2d){
ptica ^= true;
if(ptica){
g2d.drawImage(ptica1, Environment.dimension.width/2, Environment.dimension.height/2-110 + pad, null);
}
else{
g2d.drawImage(ptica2, Environment.dimension.width/2, Environment.dimension.height/2-110 + pad, null);
}
System.out.println(pad);
}
public void setSkok(boolean skok) {
this.skok = skok;
}
public void setSkokBrojac(double skokBrojac) {
this.skokBrojac = skokBrojac;
}
public double getSkokBrojac() {
return skokBrojac;
}
public boolean isSkok() {
return skok;
}
public void setPad(int pad) {
this.pad = pad;
}
public int getPad() {
return pad;
}
}
Util class:
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.IOException;
/**
* Created by Lazar on 25/05/15.
*/
public class Util {
public static BufferedImage openImage(String name){
try {
if(!name.startsWith("/slike/")){
name="/slike/"+name;
}
return ImageIO.read(Util.class.getResource(name));
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
Avoid adding all you classes to the default package, this could cause issues with class loading on some versions of Java
Painting should paint the state and should not be making decisions or changing the state
Don't, repeatedly, load resources
For example, from you Stub class, which Environment's paintComponent calls crtaj, you do the following...
public void crtaj(Graphics2D g2d){
dole = Util.openImage("stub_dole.png");
gore = Util.openImage("stub_gore.png");
g2d.drawImage(dole, dx, dy, null);
g2d.drawImage(gore, dx, -(560-dy), null);
}
Loading the images can take time. You should either have a "cache" class which managers them (loading them once) or load them when the Stub class is created (I'd prefer the cache class, as if you create and destroy many Stubs, loading the resources within the Stub class (constructor for example) could become a bottle neck
For example, which was able to go from 200-300 objects moving simultaneously, to over 4000 through the use of a re-usable object cache (rather the re-creating the objects and re-loading their resources)
Use a profiler to determine where you code is actually spending time (Note that YourKit has a 15 day free trial license available).
Once you know what your bottleneck is then determine if there's an easy fix, if not consider better algorithms and data-structures to reduce the algorithmic complexity of your code.
Profiling, as suggested by #alex-fitzpatrick, is always good. Also:
Is the type of images created by your Util.openImage call compliant with the graphics2D object you paint on? You may be spending some with the conversions (image types).
eliminate calls to getWidth() etc. You know these values after object initialization, cache them.
If possible, don't call repaint on the entire component. Use the overloaded version that specifies the area to repaint.
... and consider using JavaFX for games :-)

Slow movement using paintComponent method

I decided to re-write my game using Swing's painting technique paintComponent() method(someone on SO actually told me to use this method). I decided to use JPanel as the game's base instead of Canvas. My previous written game uses a Canvas but the game could not show up on my 64 bit desktop but could show up on my 32 bit labtop which is why this game had to be re-written.
Problem now is, while the ship's movement works, the drawing seems awfully slow(unless it is my laptop problem?) compare to what I did before which was using AWT's double buffering drawing technique. I spend a whole day but could not figure out what could possibly make the ship run faster.
public class Ship extends JLabel implements KeyListener{
private Image image;
private boolean turnRight;
private int x;
private int y;
private int speed = 5;
private boolean turnLeft;
public Ship(int x, int y)
{
this.x = x;
this.y = y;
try {
image = ImageIO.read(new File("Ship/Ship.PNG"));
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
addKeyListener(this);
}
public Image getImage()
{
return image;
}
#Override
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
if(e.getKeyCode() == KeyEvent.VK_RIGHT)
{
x += speed;
setTurnRight(true);
setTurnLeft(false);
}
else if(e.getKeyCode() == KeyEvent.VK_LEFT)
{
x -= speed;
setTurnLeft(true);
setTurnRight(false);
}
// redraw yourself
repaint();
}
private void setTurnLeft(boolean turnLeft) {
// TODO Auto-generated method stub
this.turnLeft = turnLeft;
}
// swing custom painting
public void paintComponent(Graphics g)
{
if(x <= 0)
{
x = 0;
}
else if(x >= 610)
{
x = 610;
}
g.drawImage(getImage(), x, y, null);
}
public void setTurnRight(boolean turnRight)
{
this.turnRight = turnRight;
}
public boolean getTurnLeft()
{
return turnLeft;
}
public boolean getTurnRight()
{
return turnRight;
}
#Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
}
#Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
}
Normally, I would create a concept of a renderable element. I would maintain a list of these elements and update them accordingly within my main loop.
At the very least, each would have a concept of location, direction and rotation (if required), they would also be capable of been painted.
Within my main component, I would simply loop through all these elements and "draw" them, offset the Graphics context to allow for there position within the game space.
But that's not what you are doing...
Remember, components have a sense of location and size already, you shouldn't be trying to re-implement this requirement, instead, you should be finding ways to take advantage of it...(ie, don't maintain a reference to the x/y values ;))
The following is a simple example. It uses a JPanel to render the main image. The main loop (in this case a javax.swing.Timer), tells the component that it should update it's movement as required.
The ship itself is responding to key events by changing the rotation value by a given, variable, delta. This allows you to control the speed of the spin as you need (I've deliberately set it low to start with, so play around with it)
What you should resist doing, is changing the frame rate ;)
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.border.LineBorder;
public class BattleShipGame {
public static void main(String[] args) {
new BattleShipGame();
}
public BattleShipGame() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new OceanPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class OceanPane extends JPanel {
private BattleShip ship;
public OceanPane() {
setLayout(new GridBagLayout());
ship = new BattleShip();
add(ship);
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
ship.move();
revalidate();
repaint();
}
});
timer.setRepeats(true);
timer.setCoalesce(true);
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
}
public static class BattleShip extends JPanel {
protected static final int MAX_TURN_RATE = 5;
private BufferedImage ship;
private float angle;
private float angleDelta;
public BattleShip() {
setOpaque(false);
try {
ship = ImageIO.read(new File("BattleShip.png"));
} catch (IOException ex) {
ex.printStackTrace();
}
setFocusable(true);
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "leftTurn");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "rightTurn");
am.put("leftTurn", new TurnAction(-0.1f));
am.put("rightTurn", new TurnAction(0.1f));
}
public void move() {
angle += angleDelta;
}
public void setAngle(float angle) {
this.angle = angle;
}
public float getAngle() {
return angle;
}
#Override
public Dimension getPreferredSize() {
Dimension size = new Dimension(0, 0);
if (ship != null) {
double rads = Math.toRadians(getAngle());
double sin = Math.abs(Math.sin(rads)), cos = Math.abs(Math.cos(rads));
int w = ship.getWidth();
int h = ship.getHeight();
size.width = (int) Math.floor(w * cos + h * sin);
size.height = (int) Math.floor(h * cos + w * sin);
}
return size;
}
#Override
public Dimension getMinimumSize() {
return getPreferredSize();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (ship != null) {
Graphics2D g2d = (Graphics2D) g.create();
double rads = Math.toRadians(getAngle());
double sin = Math.abs(Math.sin(rads)), cos = Math.abs(Math.cos(rads));
int w = ship.getWidth();
int h = ship.getHeight();
int newWidth = (int) Math.floor(w * cos + h * sin);
int newHeight = (int) Math.floor(h * cos + w * sin);
AffineTransform at = new AffineTransform();
at.translate((newWidth - w) / 2, (newHeight - h) / 2);
at.rotate(Math.toRadians(getAngle()), w / 2, h / 2);
g2d.drawImage(ship, at, this);
g2d.dispose();
}
}
protected class TurnAction extends AbstractAction {
protected float delta;
public TurnAction(float delta) {
this.delta = delta;
}
#Override
public void actionPerformed(ActionEvent e) {
angleDelta += delta;
if (angleDelta > MAX_TURN_RATE) {
angleDelta = MAX_TURN_RATE;
} else if (angleDelta < (MAX_TURN_RATE * -1)) {
angleDelta = (MAX_TURN_RATE * -1);
}
}
}
}
}
I would recommend having a class which extends JPanel, using a javax.swing.Timer in there, defining your 1000/fps and your ActionListener, in which you use a repaint() which uses a paintComponent that you will make that would call upon the draw method in your Ship, which is now called paintComponent.
So, as that explaination was terrible, here is some code:
public class Class_Name extends JPanel()
{
Ship ship = new Ship(0,0);
javax.swing.Timer timer = new javax.swing.Timer(1000/60, new ActionListener(){
repaint();
});
public void paintComponent(Graphics g)
{
super.paintComponent();
ship.draw(g);
}
}
and the draw is, what is now called paintComponent.
If this didn't answer your question, please let me know.

Categories