5
\$\begingroup\$

I am in the process of making a game, and during this endeavour, I have come across problems maintaining a good frames per second while drawing my sprites. When I draw my background image, my frames drop from ~65 to ~30. My background image is simply a green tile 2000x2000 wide, constructed from 900 50*50 tiles.

What can I do to increase my frames per second?

My Main class

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.*;

import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.Timer;

public class Main extends JPanel implements ActionListener, KeyListener{

    static int WIDTH;
    static int HEIGHT;
    static int SCREENWIDTH;
    static int SCREENHEIGHT;
    static int GRAVITY = 2;

    BufferedImage background;
    Tower tower;
    Entity debug = new Entity(0, 0, 0, 30, 30, 300);
    ArrayList<Entity> entities = new ArrayList<Entity>();
    Random random = new Random();

    public Main(){
        addKeyListener(this);
        setFocusable(true);
        setFocusTraversalKeysEnabled(false);
        requestFocus();

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                gameLoop();
            }
        });
        thread.start();

        tower = new Tower(WIDTH, HEIGHT, 10, 10, 7);
        background = generateBackground();
        entities.add(debug);

    }
    public void paintComponent(Graphics g){
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g;
        AffineTransform plain = g2d.getTransform();

        float xscale = (float)SCREENWIDTH/WIDTH;
        float yscale = (float)SCREENHEIGHT/HEIGHT;

        g2d.scale(xscale, yscale);
        g2d.rotate(Math.toRadians(45), WIDTH/2, HEIGHT/2);

        AffineTransform normal = g2d.getTransform();
        g2d.drawImage(background, 0, 0, this);

        for (int e=0;e<entities.size();e++){
            Entity entity = entities.get(e);
            if (entity.x + entity.w >= tower.x-tower.d && entity.x < tower.x + tower.w || entity.y + entity.h >= tower.y-tower.d && entity.y < tower.y + tower.h){
                entity.draw(g2d, normal, this);
            }
        }

        for (int i=0;i<tower.map.size();i++){
            for (int j=0;j<tower.map.get(i).size();j++){
                for (int k=0; k<tower.map.get(i).get(j).size();k++){
                    Block renderBlock = tower.map.get(i).get(j).get(k); 
                    int x = renderBlock.x;int y = renderBlock.y;int w = renderBlock.w;int h = renderBlock.h;int z = renderBlock.z;int d = renderBlock.d;
                    //block
                    g2d.setTransform(normal);

                    g2d.drawImage(renderBlock.top1, x-d-z,y-d-z, this);

                    g2d.transform(AffineTransform.getShearInstance(1, 0));
                    g2d.drawImage(renderBlock.side1, x-y-w, y-z-(d-h), this);

                    g2d.setTransform(normal);
                    g2d.transform(AffineTransform.getShearInstance(0, 1));
                    g2d.drawImage(renderBlock.side2, x-z-(d-w), y-x-h, this);

                }       
            }
        }

        for (int e=0; e<entities.size();e++){
            Entity entity = entities.get(e);
            if (!(entity.x + entity.w >= tower.x-tower.d && entity.x < tower.x + tower.w) || !(entity.y + entity.h >= tower.y-tower.d && entity.y < tower.y + tower.h) || entity.z >= tower.d){
                entity.draw(g2d, normal, this);
            }
        }

        FPSticks++;
        g2d.setTransform(normal);
        g2d.drawLine(WIDTH/2, 0, WIDTH/2, HEIGHT);
        g2d.drawLine(0, HEIGHT/2, WIDTH, HEIGHT/2);

        g2d.setTransform(plain);

        g2d.drawString("FPS: " + currentFPS + " | Logic Ticks: " + currentTPS, 0, 10);
        g2d.drawLine(SCREENWIDTH/2, 0, SCREENWIDTH/2, SCREENHEIGHT);
        g2d.drawLine(0, SCREENHEIGHT/2, SCREENWIDTH, SCREENHEIGHT/2);
    }

    public void update(){
        if (debug.velx != 0 || debug.vely != 0 || debug.velz != 0){
            //say("x: " + debug.x);
            //say("y: " + debug.y);
        }
        debug.z-=GRAVITY;
        doCollision();
        debug.update();
    }
    public void doCollision(){
        for (int e=0; e<entities.size();e++){
            Entity entity = entities.get(e);
            if (entity.x+entity.w>=tower.x && entity.x<=tower.x+tower.w && entity.y+entity.h>=tower.y && entity.y<=tower.y+tower.h){
                int leftDistance = (entity.x+entity.w)-tower.x;
                int rightDistance = entity.x-(tower.x+tower.w);
                int topDistance = (entity.y+entity.h)-tower.y;
                int bottomDistance = entity.y-(tower.y+tower.h);
                int[] distanceArr = {leftDistance, rightDistance, topDistance, bottomDistance};
                int[] smallestArr = findSmallest(distanceArr);
                int index = smallestArr[1];

                if (entity.z < tower.d){
                    entity.z+=GRAVITY;
                }
                if (entity.z < tower.d){
                    if (index == 0){
                        entity.x-=entity.speed;
                    } else if (index == 1){
                        entity.x+=entity.speed;
                    } else if (index == 2){
                        entity.y-=entity.speed;
                    } else if (index == 3){
                        entity.y+=entity.speed;
                    }
                }
            }
        }
    }
    public BufferedImage generateBackground(){
        Image grass1 = null;
        File grass1f = new File("grass1.png");
        File backgroundf = new File("background.png");
        try {
            grass1 = ImageIO.read(grass1f);
        }catch (Exception e){

        }
        background = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
        Graphics g = background.getGraphics();
        for (int h=0;h<HEIGHT/50;h++){
            for (int w=0;w<WIDTH/50;w++){
                g.drawImage(grass1, w*50, h*50, this);
            }
        }
        try {
            ImageIO.write(background, "PNG", backgroundf);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return background;
    }
    public int[] findSmallest(int[] arr){
        int smallest = arr[0];
        int index = 0;
        for (int i=0; i<arr.length;i++){
            if (arr[i]<0){
                arr[i]*=-1;
            }
            if (arr[i]<smallest){
                smallest=arr[i];
                index = i;
            }
        }
        int[] returnArr = {smallest, index};
        return returnArr;       
    }
    public static void main(String[] args){
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        SCREENWIDTH = (int) screenSize.getWidth();
        SCREENHEIGHT = (int) screenSize.getHeight();
        //SCREENWIDTH = 800;
        //SCREENHEIGHT = 600;
        WIDTH = 2000;
        HEIGHT = 2000;
        Main main = new Main();
        JFrame frame = new JFrame();
        frame.setTitle("360 ATTACK");
        frame.setSize(SCREENWIDTH, SCREENHEIGHT);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setUndecorated(true);
        frame.setLocationRelativeTo(null);
        frame.add(main);
        frame.setVisible(true);
    }
    long targetFPS = 60;
    long currentFPS = targetFPS;
    long currentTPS = targetFPS;
    long FPSticks = 0;
    long TPSticks = 0;
    long oldFPSTime = time();
    long newFPSTime = oldFPSTime;
    public void gameLoop(){
        long previous = time();
        long lag = 0;
        while (true){
            long current = time();
            long elapsed = current-previous;
            previous = current;
            lag+= elapsed;
            while (lag >= 1000/targetFPS){
                update();
                lag-= 1000/targetFPS;
                TPSticks++;
            }
            repaint();
            newFPSTime = time();
            if (newFPSTime > oldFPSTime + 1000){
                oldFPSTime = newFPSTime;
                currentFPS = FPSticks;
                currentTPS = TPSticks;
                FPSticks = 0;
                TPSticks = 0;
            }
        }
    }
    @Override

    public void actionPerformed(ActionEvent e) {

    }
    public long time(){
        return System.currentTimeMillis();
    }
    public void say(String str){
        System.out.println(str);
    }
    @Override
    public void keyPressed(KeyEvent e) {
        int c = e.getKeyCode();
        if (c == KeyEvent.VK_ESCAPE){
            System.exit(0);
        }
        if (c == KeyEvent.VK_W){
            debug.vely = -2;
        }
        if (c == KeyEvent.VK_S){
            debug.vely = 2;
        }
        if (c == KeyEvent.VK_A){
            debug.velx = -2;
        }   
        if (c == KeyEvent.VK_D){
            debug.velx = 2;
        }
        if (c == KeyEvent.VK_Q){
            debug.velz = -2-GRAVITY;
        }
        if (c == KeyEvent.VK_E){
            debug.velz = 2+GRAVITY;
        }
    }
    @Override
    public void keyReleased(KeyEvent e) {
        int c = e.getKeyCode();
        if (c == KeyEvent.VK_W){
            debug.vely = 0;
        }
        if (c == KeyEvent.VK_S){
            debug.vely = 0;
        }
        if (c == KeyEvent.VK_A){
            debug.velx = 0;
        }   
        if (c == KeyEvent.VK_D){
            debug.velx = 0;
        }
        if (c == KeyEvent.VK_Q){
            debug.velz = 0;
        }
        if (c == KeyEvent.VK_E){
            debug.velz = 0;
        }
    }
    @Override
    public void keyTyped(KeyEvent e) {

    }
}

Tower class

import java.util.ArrayList;

public class Tower {
    ArrayList<ArrayList<ArrayList<Block>>> map = new ArrayList<ArrayList<ArrayList<Block>>>();
    int blockw = 30, blockh = 30, blockd = 30;
    int WIDTH;int HEIGHT;
    int x;int y;int z;int d;int w;int h;
    public Tower(int WIDTH, int HEIGHT, int towerw, int towerh, int towerd){

        this.WIDTH = WIDTH;
        this.HEIGHT = HEIGHT;

        ArrayList<Block> blocks;
        ArrayList<ArrayList<Block>> height;
        ArrayList<ArrayList<ArrayList<Block>>> depth;

        x = ((WIDTH/2)-(towerw*blockw)/2)+(0);
        y = ((HEIGHT/2)-(towerh*blockh)/2)+(0);
        z = 0;
        d = towerd*blockd;
        w = towerw*blockw;
        h = towerh*blockh;

        depth = new ArrayList<ArrayList<ArrayList<Block>>>();
        for (int d=0; d<towerd;d++){
            height = new ArrayList<ArrayList<Block>>();
            for (int h=0; h<towerh;h++){
                blocks = new ArrayList<Block>();
                for (int w=0; w<towerw;w++){
                    blocks.add(new Block(x+(w*blockw), y+(h*blockh), (d*blockd), blockw, blockh, blockd));
                }
                height.add(blocks);
            }
            depth.add(height);
        }
        map = depth;
    }
}

Entity class

import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.geom.AffineTransform;
import java.awt.geom.Path2D;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class Entity {
    int velx = 0;
    int vely = 0;
    int velz = 0;
    int speed = 2;
    int x; int y; int z; int w; int h; int d;
    Image side1;
    Image side2;
    Image top1;
    public Entity(int x, int y, int z, int w, int h, int d){
        this.x = x;
        this.y = y;
        this.z = z;
        this.w = w;
        this.h = h;
        this.d = d;
        assignImages();
    }
    private void assignImages(){
        File fside1 = new File("side1.png");
        File fside2 = new File("side2.png");
        File ftop1 = new File("top1.png");
        try {
            side1 = (ImageIO.read(fside1)).getScaledInstance(w, d, Image.SCALE_DEFAULT);
            side2 = (ImageIO.read(fside2)).getScaledInstance(d, h, Image.SCALE_DEFAULT);
            top1 = (ImageIO.read(ftop1)).getScaledInstance(w, h, Image.SCALE_DEFAULT);
        } catch (IOException e) {

        }
    }
    public void update(){
        this.x+=this.velx;
        this.y+=this.vely;
        this.z+=this.velz;
        if (this.z < 0){
            this.z=0;
        }
    }
    public void draw(Graphics2D g2d, AffineTransform normal, Main observer){
        // entity
        g2d.setTransform(normal);
        g2d.drawImage(this.top1,this.x-this.d-this.z,this.y-this.d-this.z,observer);

        g2d.transform(AffineTransform.getShearInstance(1, 0));
        g2d.drawImage(this.side1, this.x-this.y-this.w, this.y-this.z-(this.d-this.h), observer);       

        g2d.setTransform(normal);   
        g2d.transform(AffineTransform.getShearInstance(0, 1));  
        g2d.drawImage(this.side2, this.x-this.z-(this.d-this.w),this.y-this.x-this.h, observer);
    }
}

Block class

import java.awt.Image;
import java.awt.Shape;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;

public class Block {
    Image side1;
    Image side2;
    Image top1;
    int x,y,z,w,h,d;
    public Block(int x, int y, int z, int w, int h, int d){
        this.x = x;
        this.y = y;
        this.z = z;
        this.w = w;
        this.h = h;
        this.d = d;
        assignImages();
    }
    private void assignImages(){
        File fside1 = new File("side1.png");
        File fside2 = new File("side2.png");
        File ftop1 = new File("top1.png");
        try {
            side1 = ImageIO.read(fside1);
            side2 = ImageIO.read(fside2);
            top1 = ImageIO.read(ftop1);
        } catch (IOException e) {

        }
    }
}

Images

top1, side2, side1, are all this image: enter image description here and grass1 is this image: enter image description here If you need any of the related classes, Just comment and I can add them. I'm not sure if they are necessary though to post.

\$\endgroup\$
  • 2
    \$\begingroup\$ Please do provide the other code; I cannot actually run your code to see if my ideas improve the performance without code. Also, if you have sample images and the like, it would be nice to be able to just run your code to see what happens. \$\endgroup\$ – Justin Jun 28 '16 at 7:29
  • \$\begingroup\$ Are you always going to have the same perspective tilt, or do you envision the camera rotating? \$\endgroup\$ – Justin Jun 29 '16 at 0:06
  • \$\begingroup\$ @Justin the perspective is always the same. But, i do want to draw things on the screen with different/no perspective such as menus and fps counters \$\endgroup\$ – Colourfit Jun 29 '16 at 10:39
  • \$\begingroup\$ Do you have a git repository or something that we could try to run your code ourselves? I have a feeling that your nested ArrayList is a performance breaker and that you should be using a 3d array of Block instead, Block[][][] map \$\endgroup\$ – Simon Forsberg Jun 30 '16 at 21:44
  • \$\begingroup\$ @SimonForsberg The code above is runnable. and I agree yes. \$\endgroup\$ – Colourfit Jun 30 '16 at 22:45
2
\$\begingroup\$

As strange as it sounds, the performance drain really is on this one line of code right here:

g2d.drawImage(background, 0, 0, this);

The reason is that background is a rather large image, and the g2d is performing a scale and a rotate every time. Enabling OpenGL via -Dsun.java2d.opengl=True as suggested here might help, but I haven't been able to enable it on my machine.

Do note as well that it may be worth just using an existing game engine.

That said, the solution to the problem is to take the work out of the paintComponent method. If you never will view the background from a different angle, just draw it at this tilt to begin with. Otherwise, you need yet another thread. So there would be three threads:

  1. The GUI thread
  2. Your game loop thread
  3. The rendering thread

In a proof-of-concept style, I added another volatile BufferedImage drawnBackground as a field, and I added this to the end of your Main constructor:

new Thread(() -> {
    while(true) {
        if (background == null) continue;
        // A new BufferedImage to avoid concurrency issues; in this way,
        // the reference to this.drawnBackground is always a valid image
        // to be drawn. If we reassign this.drawnBackground = drawnBackground
        // while paintComponent() is running, that's fine, as it will simply
        // draw the old image.
        // It's also important to note that assignment to references
        // is an atomic operation, but we still have to declare the field
        // as volatile
        BufferedImage drawnBackground = new BufferedImage(background.getWidth(), background.getHeight(), background.getType());
        Graphics2D g2d = (Graphics2D) drawnBackground.getGraphics();

        g2d.setColor(Color.WHITE);
        g2d.fillRect(0, 0, background.getWidth(), background.getHeight());
        float xscale = (float) SCREENWIDTH / WIDTH;
        float yscale = (float) SCREENHEIGHT / HEIGHT;
        g2d.scale(xscale, yscale);
        g2d.rotate(Math.toRadians(45), WIDTH / 2, HEIGHT / 2);
        g2d.drawImage(background, 0, 0, this);

        this.drawnBackground = drawnBackground;
    }
}).start();

The paintComponent method changes to look like so:

public void paintComponent(Graphics g){
    super.paintComponent(g);
    Graphics2D g2d = (Graphics2D) g;
    AffineTransform plain = g2d.getTransform();

    float xscale = (float)SCREENWIDTH/WIDTH;
    float yscale = (float)SCREENHEIGHT/HEIGHT;

    g2d.drawImage(drawnBackground, 0, 0, this);

    g2d.scale(xscale, yscale);
    g2d.rotate(Math.toRadians(45), WIDTH/2, HEIGHT/2);

    AffineTransform normal = g2d.getTransform();
    // g2d.drawImage(background, 0, 0, this);
    // rest of code

In this way, we move the expensive operation of rendering the image to another thread. Note that that other thread will still be getting around 30 fps instead of 60 fps, but the game itself will be running at a better framerate. In other words, the background image will be updating at closer to 30 fps, while everything else will be faster.

\$\endgroup\$
  • \$\begingroup\$ How to enable open GL with -Dsun.java2d.opengl=True? Can you provide an example please? \$\endgroup\$ – Colourfit Jun 29 '16 at 10:37
  • \$\begingroup\$ @JammehCarr It's a compiler option. If you are manually compiling, you type javac -Dsun.java2d.opengl=True whatever.java. In your IDE, find the build preferences and look for "compiler options". At some place they allow arbitrary compiler options; this can be put in there. \$\endgroup\$ – Justin Jun 29 '16 at 14:33
  • \$\begingroup\$ How to do this in eclipse? I cant seem to find it. \$\endgroup\$ – Colourfit Jun 29 '16 at 15:56
  • \$\begingroup\$ @JammehCarr In Eclipse, on the run dropdown, click "Run Configurations". Under your Java Application configurations, click on the "arguments" tab. Add that there \$\endgroup\$ – Justin Jun 29 '16 at 16:17
  • \$\begingroup\$ It works but its giving me less fps? From ~50 to ~30 now \$\endgroup\$ – Colourfit Jun 29 '16 at 16:30
6
\$\begingroup\$

Firstly, I recommend you format your code. In Eclipse, the shortcut is CTRL + SHIFT + F, in IntelliJ, it is CTRL + ALT + L. This is because of the following:

  • A lot of your functions are defined like

    public void myFunction(){
    

    We usually add a space to make it

    public void myFunction() {
    
  • You don't have any blank lines between functions. Usually we have one blank line to improve the readability.

  • Other similar formatting issues.


static int WIDTH;
static int HEIGHT;
static int SCREENWIDTH;
static int SCREENHEIGHT;
static int GRAVITY = 2;

These names seem to suggest that they are constants, so let's make them constants:

public static final int WIDTH = 2000;
public static final int HEIGHT = 2000;
public static final int SCREEN_WIDTH = Toolkit.getDefaultToolkit().getScreenSize().width;
public static final int SCREEN_HEIGHT = Toolkit.getDefaultToolkit().getScreenSize().height;
public static final int GRAVITY = 2;

However, there's no real reason why we need to separate out the ideas of width and height; why not just use the Dimension class directly? Something like this:

public static final Dimension BACKGROUND_SIZE = new Dimension(2000, 2000); // instead of WIDTH and HEIGHT
public static final Dimension SCREEN_DIMENSION = Toolkit.getDefaultToolkit().getScreenSize();
public static final int GRAVITY = 2;

BufferedImage background;
Tower tower;
Entity debug = new Entity(0, 0, 0, 30, 30, 300);
ArrayList<Entity> entities = new ArrayList<Entity>();
Random random = new Random();

Don't declare fields with default visibility. Declare them as private:

private BufferedImage background;

Also, it's better to "code to the interface", meaning that rather than using ArrayList<T>, use List<T>; most of the time, the rest of the code stays the same, but in the future, if you decide a different List, such as LinkedList, then all you have to do is change new ArrayList<Entity>() to new LinkedList<Entity>():

private List<Entity> entities = new ArrayList<>(); // Note the diamond operator, Java 7+. Use it; avoid repeating yourself.
... = new Entity(0, 0, 0, 30, 30, 300);

That is ... quite the constructor call. If you have six arguments to your constructor, one of the following is probably true:

  • Entity is doing too many things. The Single Responsibility Principle states that each class should ideally do one thing, and only one thing. Perhaps Entity should be split into multiple classes.

  • Entity should be composed of multiple classes. For example, making more classes to describe Entity's data might result in a constructor call similar to:

    new Entity(new Location(0, 0), 0, new SubEntity(30, 30, 300))
    
  • You should use the Builder design pattern. Note that this is least likely to be the correct solution. The Builder design pattern would make it so that you could construct the Entity along the lines of:

    Entity.builder()
        .setLocation(new Location(0, 0))
        .setHeight(0)
        .setSubEntity(new SubEntity(30, 30, 300)) // SubEntity is not an appropriate name; name it according to the data it holds
        .create();
    

    In this case, the design pattern basically gives you a way to pass "named arguments" to the constructor.

Random random = new Random();

I actually recommend you take this as a parameter in the constructor (this is known as Dependency Injection). In doing so, should you want to debug by setting the Random to a seed, you could just call new Main(new Random(0)) instead of having to modify this file (your main function may not always be in this file, and it is cleaner anyway). Furthermore, should you want to switch Random implementations away from the default, say to a Mersenne Twister, you'd simply pass in a new MersenneTwister() and the implementation of Main wouldn't have to change.


Thread thread = new Thread(new Runnable() {
    @Override
    public void run() {
        gameLoop();
    }
});
thread.start();

With Java 8, this code could be reduced down to:

Thread thread = new Thread(this::gameLoop);
thread.start();

public void paintComponent(Graphics g){

Good job in overriding paintComponent instead of paint!


public int[] findSmallest(int[] arr){
    int smallest = arr[0];
    int index = 0;
    for (int i=0; i<arr.length;i++){
        if (arr[i]<0){
            arr[i]*=-1;
        }
        if (arr[i]<smallest){
            smallest=arr[i];
            index = i;
        }
    }
    int[] returnArr = {smallest, index};
    return returnArr;       
}

Don't return an int[] for this. Instead, just return the index (so rename the function to indexOfSmallest). If you want the index, just use it to get the value.

Also, the function doesn't actually find the smallest value; you actually look for the closest to 0. A better name would be indexOfSmallestAbs. Also, you never access state in this function, so make it static to signal the intent.

Furthermore, this entire function can be much more concise with Java 8:

public static int indexOfSmallestAbs(int[] arr) {
    return IntStream.range(0, arr.length)
        .reduce((a, b) -> Math.abs(arr[a]) < Math.abs(arr[b]) ? a : b)
        .getAsInt();
}

Moreover, you usually shouldn't use arrays; Lists are way more convenient in general. And you also assume that the array that is passed in has a length at least 1. At the very least, you should assert this:

public static int indexOfSmallestAbs(List<Integer> values) {
    assert values.size() >= 1;
    return IntStream.range(0, values.size())
        .reduce((a, b) -> Math.abs(values.get(a)) < Math.abs(values.get(b)) ? a : b)
        .getAsInt();
}

public static void main(String[] args){
    Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
    SCREENWIDTH = (int) screenSize.getWidth();
    SCREENHEIGHT = (int) screenSize.getHeight();
    //SCREENWIDTH = 800;
    //SCREENHEIGHT = 600;
    WIDTH = 2000;
    HEIGHT = 2000;
    Main main = new Main();
    JFrame frame = new JFrame();
    frame.setTitle("360 ATTACK");
    frame.setSize(SCREENWIDTH, SCREENHEIGHT);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setUndecorated(true);
    frame.setLocationRelativeTo(null);
    frame.add(main);
    frame.setVisible(true);
}

In particular:

    JFrame frame = new JFrame();
    frame.setTitle("360 ATTACK");
    frame.setSize(SCREENWIDTH, SCREENHEIGHT);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setUndecorated(true);
    frame.setLocationRelativeTo(null);
    frame.add(main);
    frame.setVisible(true);

Reorganize the function calls:

JFrame frame = new JFrame("360 ATTACK"); // Just use the constructor

frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setUndecorated(true);
frame.add(main);
frame.setSize(SCREENWIDTH, SCREENHEIGHT);
frame.setLocationRelativeTo(null);
frame.setVisible(true);

This is because setLocationRelativeTo wouldn't know the width of the frame otherwise, and you'd get an off-center window. Except you appear to be trying to maximize the window, which is better done like so:

frame.setExtendedState(JFrame.MAXIMIZED_BOTH);

rather than

frame.setSize(SCREENWIDTH, SCREENHEIGHT);

public long time(){
    return System.currentTimeMillis();
}
public void say(String str){
    System.out.println(str);
}

No. Don't do this. I get that it's a pain to type out System.currentTimeMillis() or System.out.println(str), but code isn't just for writing. Adding these functions really just make it so that it takes longer to read the code; don't alias these system function calls. Good IDEs would make writing System.currentTimeMillis() as easy as (something like) Sys<tab>.curr<tab> (or whatever the auto-complete key is; I'm assuming it's tab), and System.out.println is often just sout<enter> or sysout<enter>. Use those IDE shortcuts instead of these functions you defined.

\$\endgroup\$
  • \$\begingroup\$ @Justin When can I expect an an answer for the preformance issues from you? \$\endgroup\$ – Colourfit Jun 28 '16 at 18:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.