Swing 2D game low performance - java

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 :-)

Related

elsif with nextboolean for pictures

i am making a game with cactus pictures but when i do my code if its trying to switch between pictures it will stops and will freeze on that point the else if it does not work i also use bufferedimage for the pictures
my code:
package objectgame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import util.Resource;
public class EnemiesManager {
private List<Enemy> enemies;
private Random random;
private BufferedImage imagecactus1, imagecactus2, imagecactus3 ;
public EnemiesManager() {
enemies = new ArrayList<Enemy>();
imagecactus1 = Resource.getResourceImage("data/cactus1.png");
imagecactus2 = Resource.getResourceImage("data/cactus2.png");
imagecactus3 = Resource.getResourceImage("data/cactus3.png");
random = new Random();
enemies.add(getRandomCactus());
random = new Random();
}
public void update() {
for(Enemy e: enemies) {
e.update();
}
Enemy firstEnemy = enemies.get(0);
if(firstEnemy.isOutOfScreen()) {
enemies.remove(firstEnemy);
enemies.add(getRandomCactus());
}
}
public void draw(Graphics g) {
for(Enemy e: enemies) {
e.draw(g);
}
}
private Cactus getRandomCactus() {
Cactus cactus;
cactus = new Cactus();
cactus.setX(600);
if(random.nextBoolean()) {
cactus.setImage(imagecactus1);
}
else{
cactus.setImage(imagecactus2);
}
return cactus;
}
}
new class
package objectgame;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
import java.awt.image.BufferedImage;
import util.Resource;
public class Cactus extends Enemy{
private BufferedImage image;
private int posX, posY;
private Rectangle rect;
public Cactus() {
image = Resource.getResourceImage("data/cactus1.png");
posX = 200;
posY = 83;
rect = new Rectangle();
}
public void update() {
posX -= 3;
rect.x = posX;
rect.y = posY;
rect.width = image.getWidth();
rect.height = image.getHeight();
}
#Override
public Rectangle getBound() {
return rect;
}
#Override
public void draw(Graphics g) {
g.drawImage(image, posX, posY, null);
}
public void setX(int x) {
posX = x;
}
public void setY(int y) {
posY = y;
}
public void setImage(BufferedImage images) {
this.image = images;
}
#Override
public boolean isOutOfScreen() {
return (posX + image.getWidth() < 0);
}
}
what is the problem this is the code from the anamation manager and the details from the picture. I have tried many tutorials but none where working correct always slightly off the purpose for the random bool. I use eclipse so I don't get any errors specific to the location where the error happens. I also did use debugger but non are working. So I really need your guys help.
A clean approach is as follows:
private Cactus getRandomCactus() {
Cactus cactus = new Cactus();
cactus.setX(600);
BufferedImage [] images = {imagecactus1, imagecactus2, imagecactus3};
cactus.setImage(images[random.nextInt(3)]);
return cactus;
}
Note: random.nextInt(3) returns an int from 0 to 2 i.e. either 0 or 1 or 2.
There are a couple reasons you could be running into issues, the main one being the fact that you're called Random.nextBoolean() multiple times. When you do that, it gives you a new value each time. You'd want to set a variable equal to Random.nextBoolean().
Also, since you appear to be choosing between three different images, rather than two. For this, I recommend you use Random.nextInt instead:
private Cactus getRandomCactus() {
Cactus cactus;
cactus = new Cactus();
int randomInt = random.nextInt(0, 3); // this only called random.nextInt once, and gives me a random number between 0 and 2
cactus.setX(600);
if(randomInt == 0) {
cactus.setImage(imagecactus1);
}
else if (randomInt == 1) {
cactus.setImage(imagecactus2);
}
else {
cactus.setImage(imagecactus3);
}
return cactus;
}
the problem what that there where 2 files called cactus3.png and also there was one space to much. thanks for thinking with me .

Move image in a spiral fashion in java

I am trying to make a simple animated intro. I have an image I am trying to move from the bottom left of the screen to the center of the screen in a clockwise spiral motion. This is the code that I am using for now. It just moves the image upward to the center:
static ImageLoader il = new ImageLoader();
private static BufferedImage logo = il.load("/logoNew.png");
private static Image power = il.gif("http://i.stack.imgur.com/KSnus.gif");
static double y = 1024.0;
public static void render(Graphics g){
if(y>(486/2)-128){
y = y-0.25;
}
if(draw){
g.drawImage(logo,(864/2)-128,(int)y,null);
g.setColor(Color.WHITE);
g.drawImage(power,10,10,null);
}
}
The if(draw) statement is activated by something else.
How do I go about moving the image. Do I just increment the x and the y differently at different points?
** EDIT **
I didn't make it clear on the motion. Its going from the bottom left to the top left to the top right to the bottom right to the bottom center (centre) to the center (centre) of the screen
Animation is the illusion of movement over time. Normally I would use something like the Timing Framework (or Trident or Universal Tween Engine) as the base of the animation, these provide better support for things like ease-in and ease-out.
The following example just makes uses of a simple javax.swing.Timer. I use this because it's safer to use with Swing, as it allows me to update the state of the UI from within the context of the Event Dispatching Thread, but doesn't block it (preventing it from updating the screen).
The following example uses a concept of a timeline and key frames. That is, at some point in time, something must happen. The timeline then provides the means for blending between those "key" points in time.
I, personally, like to work in abstract concepts, so the timeline is simply measured in a percentage from 0-1, which allows me to provide a variable time span. This allows me to adjust the speed of the animation without the need to change anything.
As you (should) be able to see, the last two legs only need to move half the distance, so they are slower than the other three legs, so, technically, they only need half the time to complete...but I'll leave it up to you to nut out the maths for that ;)
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.TreeMap;
import javax.imageio.ImageIO;
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 {
protected static final int PLAY_TIME = 6000;
private Timeline timeline;
private long startTime;
private Point imgPoint;
private BufferedImage img;
public TestPane() {
try {
img = ImageIO.read(new File("C:/Neko.png"));
imgPoint = new Point(0, 200 - img.getHeight());
timeline = new Timeline();
timeline.add(0f, imgPoint);
timeline.add(0.2f, new Point(0, 0));
timeline.add(0.4f, new Point(200 - img.getWidth(), 0));
timeline.add(0.6f, new Point(200 - img.getWidth(), 200 - img.getHeight()));
timeline.add(0.8f, new Point(100 - (img.getWidth() / 2), 200 - img.getHeight()));
timeline.add(1f, new Point(100 - (img.getWidth() / 2), 100 - (img.getHeight() / 2)));
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
long duration = System.currentTimeMillis() - startTime;
float progress = (float) duration / (float) PLAY_TIME;
if (progress > 1f) {
startTime = System.currentTimeMillis();
progress = 0;
((Timer) (e.getSource())).stop();
}
System.out.println(progress);
imgPoint = timeline.getPointAt(progress);
repaint();
}
});
startTime = System.currentTimeMillis();
timer.start();
} catch (IOException exp) {
exp.printStackTrace();
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (img != null && imgPoint != null) {
Graphics2D g2d = (Graphics2D) g.create();
g2d.drawImage(img, imgPoint.x, imgPoint.y, this);
g2d.dispose();
}
}
}
public static class Timeline {
private Map<Float, KeyFrame> mapEvents;
public Timeline() {
mapEvents = new TreeMap<>();
}
public void add(float progress, Point p) {
mapEvents.put(progress, new KeyFrame(progress, p));
}
public Point getPointAt(float progress) {
if (progress < 0) {
progress = 0;
} else if (progress > 1) {
progress = 1;
}
KeyFrame[] keyFrames = getKeyFramesBetween(progress);
float max = keyFrames[1].progress - keyFrames[0].progress;
float value = progress - keyFrames[0].progress;
float weight = value / max;
return blend(keyFrames[0].getPoint(), keyFrames[1].getPoint(), 1f - weight);
}
public KeyFrame[] getKeyFramesBetween(float progress) {
KeyFrame[] frames = new KeyFrame[2];
int startAt = 0;
Float[] keyFrames = mapEvents.keySet().toArray(new Float[mapEvents.size()]);
while (startAt < keyFrames.length && keyFrames[startAt] <= progress) {
startAt++;
}
if (startAt >= keyFrames.length) {
startAt = keyFrames.length - 1;
}
frames[0] = mapEvents.get(keyFrames[startAt - 1]);
frames[1] = mapEvents.get(keyFrames[startAt]);
return frames;
}
protected Point blend(Point start, Point end, float ratio) {
Point blend = new Point();
float ir = (float) 1.0 - ratio;
blend.x = (int) (start.x * ratio + end.x * ir);
blend.y = (int) (start.y * ratio + end.y * ir);
return blend;
}
public class KeyFrame {
private float progress;
private Point point;
public KeyFrame(float progress, Point point) {
this.progress = progress;
this.point = point;
}
public float getProgress() {
return progress;
}
public Point getPoint() {
return point;
}
}
}
}

Assigning an Image object causes nothing to be drawn

I have it set up so all it does is draw a light gray background and some rectangles to create a grid. My next goal was to assign two images so I can default the grid to load as one, and then eventually add some listeners to let me change them to the other.
However, when I assign the image (png format) to the Image object through an ImageIcon (created and assigned previously and creating a new one on the spot were tested) it causes the default background color to be shown, and nothing drawn.
This occurs no matter where I move the assignments to except if I put the assignment into the draw method itself.
The image that I'm trying to use works fine, I pulled it straight from a previously working project, the location hadn't changed. But to be sure, I copied the address and put it in again.
Here is the code (The assignment causing the issue is in Block):
Main:
package BLURGpackage;
import javax.swing.JFrame;
public class Main extends JFrame
{
private final int FRAME_WIDTH = 1200, FRAME_HEIGHT = 1034; // add 34 onto height
//to show all of the panel
public Main()
{
setSize(FRAME_WIDTH, FRAME_HEIGHT);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setResizable(false);
setVisible(true);
Panel panel = new Panel();
add(panel);
}
public static void main(String[] args)
{
Main m = new Main();
}
}
Panel:
package BLURGpackage;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Toolkit;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
public class Panel extends JPanel implements Runnable
{
public final static int PANEL_WIDTH = 1200, PANEL_HEIGHT = 1000;
private Dimension panelDimension = new Dimension(PANEL_WIDTH, PANEL_HEIGHT);
private Thread panelThread = null;
private boolean threadRunning = false;
private Image dbi = null;
private Graphics dbg = null;
Grid grid;
private int mouseX = 0, mouseY = 0;
// ============ CHANGE THESE TO CHANGE THE LOCATION OF WHERE THE X AND Y COORDS ARE PRINTED ON THE SCREEN =============
private int printXCoordsX = 10; // X-Coordinate for where to print the current mouse X location
private int printXCoordsY = 855; // Y-Coordinate for where to print the current mouse X location
private int printYCoordsX = printXCoordsX; // X-Coordinate for where to print the current mouse Y location
private int printYCoordsY = printXCoordsY + 15; // Y-Coordinate for where to print the current mouse Y location
// ====================================================================================================================
public Panel()
{
grid = new Grid();
setPreferredSize(panelDimension);
setBackground(Color.LIGHT_GRAY);
// ===== Listeners ==========
addMouseMotionListener(new MouseMotionAdapter()
{
public void mouseMoved(MouseEvent e)
{
mouseX = e.getX();
mouseY = e.getY();
}
});
// ====== Start ============= (if startPanel is before listeners, dbi is null... dunno why)
startPanel();
}
private void startPanel()
{
if(panelThread == null || threadRunning == false)
{
panelThread = new Thread(this);
threadRunning = true;
panelThread.start();
}
else
System.out.println("panelThread is NOT null on startup");
}
#Override
public void run()
{
while(threadRunning)
{
update();
render();
paintScreen();
}
}
public void update()
{
// Logic here
}
public void render()
{
if(dbi == null)
{
dbi = createImage(PANEL_WIDTH, PANEL_HEIGHT);
if(dbi == null)
System.err.println("dbi is NULL");
else
{
dbg = dbi.getGraphics();
}
}
dbg.setColor(Color.LIGHT_GRAY);
dbg.fillRect(0, 0, PANEL_WIDTH, PANEL_HEIGHT);
draw(dbg);
}
public void paintScreen()
{
Graphics g;
try
{
g = this.getGraphics();
if(dbi != null && g != null)
{
g.drawImage(dbi, 0, 0, null);
}
Toolkit.getDefaultToolkit().sync();
g.dispose();
}catch(Exception e)
{
e.printStackTrace();
}
}
public void draw(Graphics g)
{
g.setColor(Color.BLACK);
g.drawString("X: "+mouseX, printXCoordsX, printXCoordsY);
g.drawString("Y: "+mouseY, printYCoordsX, printYCoordsY);
// Allow the Grid to draw
grid.draw(g);
}
}
Grid:
package BLURGpackage;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Image;
import javax.swing.ImageIcon;
public class Grid
{
// ======== CHANGE THESE VARIABLES TO CHANGE THE SIZE OF THE GRID =============
public final static int GRID_ROWS = 21, GRID_COLUMNS = 25;
private final int RECT_WIDTH = 48, RECT_HEIGHT = 40;
private final int NUM_BLOCKS = GRID_ROWS*GRID_COLUMNS;
/*public final static int GRID_ROWS = 25, GRID_COLUMNS = 25;
private final int RECT_WIDTH = Panel.PANEL_WIDTH / GRID_ROWS; // 1200 / 25 = 48 px width per rect
private final int RECT_HEIGHT = Panel.PANEL_HEIGHT / GRID_COLUMNS; // 1000 / 25 = 40 px height per rect
private final int NUM_BLOCKS = GRID_ROWS*GRID_COLUMNS;*/
// ============================================================================
private Block[] blockArray;
private int[] blockSelectArray; // numbers for which block is selected. Feed in through file reader
public Grid()
{
// ===== FOR TESTING ONLY======
blockArray = new Block[NUM_BLOCKS];
blockSelectArray = new int[NUM_BLOCKS];
for(int i=0; i < NUM_BLOCKS; i++)
{
blockSelectArray[i] = 0;
}
// ============================
loadBlocks();
}
private void loadBlocks()
{
int x, y;
x = y = 0;
for(int i = 0; i < NUM_BLOCKS; i++)
{
if(x >= Panel.PANEL_WIDTH) // if x hits right boundary, reset x and increment y by the px height of a single block
{
x = 0;
y += RECT_HEIGHT;
}
blockArray[i] = new Block(blockSelectArray[i], x, y);
x += RECT_WIDTH;
}
}
public void draw(Graphics g)
{
g.setColor(Color.BLACK);
for(int i=0;i<NUM_BLOCKS;i++)
{
g.drawRect(blockArray[i].getBlockX(), blockArray[i].getBlockY(), RECT_WIDTH, RECT_HEIGHT);
blockArray[i].draw(g);
}
}
}
Block:
package BLURGpackage;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Rectangle;
import javax.swing.ImageIcon;
public class Block
{
private Image blockImage = null;
private Image DIRT_IMG = new ImageIcon("C:/Users/Tyler/workspace/IntTut1/src/thejavahub/images/dirt.png").getImage();
// ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ Problem assignment
private Rectangle blockRect = null;
private boolean solid;
private final int RECT_WIDTH = Panel.PANEL_WIDTH / Grid.GRID_ROWS; // 1200 / 25 = 48 px width per rect
private final int RECT_HEIGHT = Panel.PANEL_HEIGHT / Grid.GRID_COLUMNS; // 1000 / 25 = 40 px height per rect
public Block(int blockNum, int x, int y)
{
// switch statement on blockNum for what block
switch(blockNum)
{
default:
System.out.println("Default of switch in Block constrcutor hit");
break;
case 0: // background block
solid = false;
break;
case 1: // dirt block
blockImage = DIRT_IMG; // <---- uses the assigned image --------
solid = true;
break;
}
// set the coordinates of the block
blockRect = new Rectangle(x, y, RECT_WIDTH, RECT_HEIGHT);
}
public void draw(Graphics g)
{
// draw the image here (.... g.drawImage(blockImage, blockRect.x, blockRect.y, null); )
g.drawImage(blockImage, blockRect.x, blockRect.y, null);
}
// ==== GETTERS AND SETTERS ===
public int getBlockX()
{
return this.blockRect.x;
}
public void setBlockX(int x)
{
this.blockRect.x = x;
}
public int getBlockY()
{
return this.blockRect.y;
}
public void setBlockY(int y)
{
this.blockRect.y = y;
}
public boolean isSolid() {
return solid;
}
public void setSolid(boolean solid) {
this.solid = solid;
}
}
Some Points:
Call frame.setVisible(true) in the end after adding all the components.
Use frame.pack() instead of frame.setSize() that fits the components as per component's preferred size.
Override getPreferredSize() to set the preferred size of the JPanel in case of custom painting.
Use Swing Timer instead of Thread that is most suitable for swing application in two ways:
To perform a task once, after a delay.
To perform a task repeatedly.
Read more How to Use Swing Timers
Use SwingUtilities.invokeLater() or EventQueue.invokeLater() to make sure that EDT is initialized properly.
Read more
Why to use SwingUtilities.invokeLater in main method?
SwingUtilities.invokeLater
Should we use EventQueue.invokeLater for any GUI update in a Java desktop application?
sample code:
private Timer timer;
...
// 1 second delay
timer = new javax.swing.Timer(1000, new ActionListener() {
#Override
public void actionPerformed(ActionEvent arg0) {
// next call
}
});
timer.setRepeats(true);
timer.start();
public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
Main m = new Main();
}
});
}
class MyRoll extends JPanel {
// override paintComponent in case of custom painting
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
...
}
#Override
public Dimension getPreferredSize() {
return new Dimension(..., ...);
}
}

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.

How to fix bad Double-Buffering [closed]

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.

Categories