-1

I"m new to Java and Stack Overflow, sorry if this is a bad question. So i created a Game in eclipse which is basically an animation with a key press jumping over other animations. I used Swing for my GUI, still new to them, and ran it in eclipse no worries. I decided to export it as a Runnable jar file to my Desktop, did so with out any hassles. The trouble begins when i open the Runnable Jar. The speed the animations play at are incredibly slow. I ran the Runnable jar on another pc with similar specs, I have a HP laptop with windows 10, and it ran fine. The Runnable jar sometimes runs at the correct speed and sometimes it doesn't, mostly doesn't, any help appreciated. my code:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;

public class GB extends JPanel implements ActionListener, KeyListener {
    /**
     * 
     */
    private static final long serialVersionUID = 1L;
    Timer timer = new Timer(5, this);
    Timer jump = new Timer(16, null);
    int x = 140;
    int y = 240; 
    int roof = 60;
    int vely =0;
    int velx = 0;
    int play;
    int gamestate;
    int blockstate;
    int blockvel = 5;
    int jumpstate = 1;
    int count = 1;
    int score;
    int block1 =1600;
    int block2 =2000;
    int block3 =2500;
    int block4 =2900;
    int block5 =3300;
    int block6 =3900;
    int block7 =4350;
    int block8 =4850;
    int block9 =5350;
    int casm;
    int floor = 240;
    boolean fall = false;

    public GB() {
        timer.start(); 
        jump.start();
        addKeyListener(this);
        setFocusable(true);
        setFocusTraversalKeysEnabled(false);
    }

    public void paintComponent(Graphics g) {
        if(gamestate == 0){
            super.paintComponent(g);
            setBackground(Color.gray);
            g.drawString("press enter to play", 500, 200);
        }

        if(gamestate == 1){
            super.paintComponent(g);
            g.drawString("Score =" + score, 50, 20);
            setBackground(Color.gray);
            g.setColor(Color.cyan);
            g.fillRect(0, 270, 1500, 300);

            if(play == 0 && gamestate == 1){
            super.paintComponent(g);
            setBackground(Color.gray);
            g.setColor(Color.cyan);
            g.fillRect(0, 270, 1500, 300);
            g.setColor(Color.black);
            g.drawString("Press Space to begin" , 500, 300);
            g.drawString("Score =" + score, 50, 20);
            }

            if(gamestate == 1){
            g.fillRect(block1,150,50,150);
            g.fillRect(block2,150, 50, 150);
            g.fillRect(block3,0,50,220);
            g.fillRect(block4,150,50,150);
            g.fillRect(block6, 150 , 50, 150);
            g.fillRect(block7, 0, 50, 220);
            g.fillRect(block9, 0, 50, 220);
            g.setColor(Color.gray);
            g.fillRect(block5, 240, 200, 300);
            g.fillRect(block8, 240, 200, 300);
            }

            g.setColor(Color.black);
            g.fillRect(x,y,30,30);

            if(jumpstate == 2 ){
                vely = -6; 
                if(y < roof ){
                    jumpstate = 3;
                    if (jumpstate == 3){
                        vely = 6;
                        jumpstate = 4;
                    }
                }
            }
            if (y == floor && jumpstate == 4){
                vely = 0;
                jumpstate = 1;
            }

            if((x > block1 && x < block1 + 49) && y > 150){
                gamestate = 2;
            }

            if((x > block2 && x < block2 + 49) && y > 150){
                gamestate = 2;
            }

            if((x > block3 && x < block3 + 49) && y < 220){
                gamestate = 2;
            }

            if((x > block4 && x < block4 + 49) && y > 150){
                gamestate = 2;
            }

            if((x > block5 && x < block5 + 199) && (y == 240 && fall == false)){
                fall = true;
                if(fall == true){
                    vely = 6;
                }
            }

            if((x > block6 && x < block6 + 49) && y > 150){
                gamestate = 2;
            }

            if((x > block7 && x < block7 + 49) && y < 220){
                gamestate = 2;
            }

            if((x > block8 && x < block8 + 199) && (y == 240 && fall == false)){
                fall = true;
                if(fall == true){
                    vely = 6;
                }
            }

            if((x > block9 && x < block9 + 49) && y < 220){
                gamestate = 2;
            }

            if (y > 500){
                gamestate = 2;
            }


            if (gamestate == 2){
                super.paintComponent(g);
                vely = 0;
                g.drawString("game over, your Score was:" + score + " Metres", 500, 200);
                g.drawString("press r to play again" , 500, 225);
            }
        }
    }

    public void actionPerformed(ActionEvent e) {

        if(gamestate == 1 && play == 1){
        block1 = block1 - blockvel;
        block2 = block2 - blockvel;
        block3 = block3 - blockvel;
        block4 = block4 - blockvel;
        block5 = block5 - blockvel;
        block6 = block6 - blockvel;
        block7 = block7 - blockvel;
        block8 = block8 - blockvel;
        block9 = block9 - blockvel; 
        score = score + 1;
        }

        if(score == 2000){
            blockvel = 7;
        }

        if(score == 5000){
            blockvel = 9;
        }

        if(score == 7000){
            blockvel = 11;
        }

        if (block1 < 0){
            block1 = block9 + 500;
        }

        if (block2 < 0){
            block2 = block1 + 500;
        } 

        if (block3 < 0){
            block3 = block2 + 500;
        }

        if (block4 < 0){
            block4 = block3 + 500;
        }

        if (block5 < -200){
            block5 = block4 + 500 ;
        }

        if (block6 < 0){
            block6 = block5 + 500;
        }

        if (block7 < 0){
        block7 = block6 + 500;
        }

        if (block8 < -200){
            block8 = block7 + 500;
        }

        if (block9 < 0){
            block9 = block8 + 500;
        }


        x = x + velx;
        y = y + vely;

        if( y < 60){
            vely = 0;
        }

       //if( y > 240 ){
        //  vely = 0;
       // }

        repaint();
    }


    public void keyPressed(KeyEvent e) {
        int code = e.getKeyCode();

        if (code == KeyEvent.VK_SPACE ){
            jumpstate = 2;
            play = 1;
        }

        if (code == KeyEvent.VK_ENTER){
            gamestate = 1;
            blockstate = 1;
            }

        if(code == KeyEvent.VK_R){
            gamestate = 0;
            block1 = 1600;
            block2 = 2000;
            block3 = 2500;
            block4 = 2900;
            block5 = 3300;
            block6 = 3900;
            block7 = 4350;
            block8 = 4850;
            block9 = 5350;
            vely = 0;
            velx = 0;
            y = 240;
            x = 140;
            play = 0;
            score = 0;
            blockvel = 5;
            fall = false;
        }
    }


    public void keyTyped(KeyEvent e) {}

    public void keyReleased(KeyEvent e) {}


    public static void main (String args[]){

        JFrame Frame = new JFrame();
        GB c = new GB();
        Frame.add(c);
        Frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        Frame.setSize(1365,500);
        Frame.setVisible(true);
        Frame.setResizable(false);

    }
}
G.Man
  • 1
  • 1
  • What version of Java is eclipse using and what version of Java is available at the command line? – Thorbjørn Ravn Andersen Feb 27 '18 at 00:15
  • Use a single `Timer`, manage the whole state from within it - don't use `paintComponent` to modify the state, painting should only paint the current state – MadProgrammer Feb 27 '18 at 00:23
  • I'm using eclipse oxygen and my JRE is 9.0.4. also can you give me an example of how i would manage it within. – G.Man Feb 27 '18 at 00:34
  • [Gravity based bounce](https://stackoverflow.com/questions/16493809/how-to-make-sprite-jump-in-java/16494178#16494178) – MadProgrammer Feb 27 '18 at 00:38
  • You should also considering using multiple views, rather then trying to do everything in the a single class. This would allow you to switch out menus or game over states more easily – MadProgrammer Feb 27 '18 at 00:56
  • Forgive me, but how do you mean @MadProgrammer. – G.Man Feb 27 '18 at 01:12

2 Answers2

0

Some computer will be faster - some will be slower. Even on the same computer, you will have busy vs non. busy time e.g. when another process needs the CPU, your process will wait for CPU.

You should synchronize the cycle time on each machine, for example, by regulating the iterations /sec - so that each machine works at the same speed.

For example, adding the following logic, makes sure that a cycle takes at least 10ms (you can up or lower this time). For faster machines, it will sleep longer and on slower machine it will sleep less to maintain the same speed. Though if a machine cannot complete a cycle in 10 ms - it will slow down even more.

public void actionPerformed(ActionEvent e) {
    final int millisBetweenCycle = 10;  //-- this many millis must have elapsed before starting the new cycle
    long cycleTime =  System.currentTimeMillis() - this.lastActionPerformedTime;
    long waitTime = millisBetweenCycle - cycleTime;
    if (waitTime > 0) {
        try {
            System.out.println("Sleeping for ms.: " + waitTime);
            Thread.sleep(waitTime);
        } catch (InterruptedException e1) {
            e1.printStackTrace();
        }
    }
    this.lastActionPerformedTime = System.currentTimeMillis();

UPDATE: Based on the comments, consider Timer instead of sleep to perform something similar. Consider the above example a proof of concept to see if this will work for you, before re-writing your program to use a timer.

Ari Singh
  • 1,228
  • 7
  • 12
  • The `Timer` calls the `actionPerformed` within the context of the Event Dispatching Thread, calling `Thread.sleep` within the context of the EDT will prevent it from performing any painting or respond to any input events by the user - basically "hanging" the program - this is NOT a good idea. There are other influences that I would consider more important then trying to "flatten out" the timings - like how fast `paintComponent` runs – MadProgrammer Feb 27 '18 at 00:54
  • Update; i put the logic in and it has improved it, however the animation in the runnable jar still runs a fraction slower than in eclipse. – G.Man Feb 27 '18 at 01:12
  • Make your logic to refresh once every 20ms or so - that is 50 frames per second - more than enough for a human eye. Move more in each refresh. – Ari Singh Feb 27 '18 at 01:47
  • For the long term it should be Timer - for your tests it could be either – Ari Singh Feb 27 '18 at 02:26
0

Background

Swing uses a passive rendering algorithm. This means that painting may occur for any number of reasons, many of which you don't control.

In Swing, you make "requests" for paint cycle to the RepaintManager via repaint, it's then up to the RepaintManager to decide when and what will get repainted.

To this end, you should not modify the game or UI state from within the paint methods, painting should paint the current state and nothing else.

Also, painting should be done as quickly as possible, any extended delay will cause the UI to stutter and become unresponsive.

Swing is also NOT thread safe and is single threaded. Basically this means:

  • You should perform any operation on the Event Dispatching Thread which may block or take time to run. Doing so will prevent the EDT from updating the UI and respond to user input in a timely manner
  • You should not modify the state of the UI (or a state which the UI depends on) from outside the context of the EDT. This could result in race conditions and predictable results

Recommendations

KeyListener

KeyListener, generally is a poor choice and despite your attempt to reduce the risk, it is a hacked approach at best.

A better choice is to make use of the Key Bindings API which provides better mechanisms for fixing/controller these focus related issues.

Decouple

Start by decoupling the game state from the UI/rendering. This will allow you to modify the game state independently and means that the renderer is focused solely on rendering the current state of the model.

Now, this is REALLY basic. Normally I'd decouple it further, having the blocks been independent entities, but this is basically what you state concept current looks like

public class GameModel {

    int x = 140;
    int y = 240;
    int roof = 60;
    int vely = 0;
    int velx = 0;
    int play;
    int gamestate;
    int blockstate;
    int blockvel = 5;
    int jumpstate = 1;
    int count = 1;
    int score;
    int block1 = 1600;
    int block2 = 2000;
    int block3 = 2500;
    int block4 = 2900;
    int block5 = 3300;
    int block6 = 3900;
    int block7 = 4350;
    int block8 = 4850;
    int block9 = 5350;
    int casm;
    int floor = 240;
    boolean fall = false;
}

(and yes, I'd normally include setters and getters to allow it to better manage it's own state)

Multiple Views

Rather then dumping all your various state logic into a single view, separate them into something which is more manageable and focused.

Based on your code, this might mean using "game", "menu" and "game over" views, each responsible for displaying and managing the various states. This will help reduce unwanted code (and complexity) from the primary game view.

And in the darkness, bind them all...

Finally, in order to manage the various views, you'll probably want some kind of controller. This provides a communication and logic flow between the various views. For example, the "menu" view doesn't care "how" you start a game, it only cares that when triggered, it can get the game started.

This further decouples the code and makes the management easier, as the various views are relieved of any navigation responsibility beyond making a request to the controller.

Example...

Okay, that's all find and good, but how does all that actually work?

Well, something like this..

import java.awt.CardLayout;
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
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 GB {

    public static void main(String args[]) {
        new GB();
    }

    public GB() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                JFrame Frame = new JFrame();
                Frame.add(new Game());
                Frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                Frame.setResizable(false);
                Frame.setSize(1365, 500);
                Frame.setVisible(true);
            }
        });
    }

    public class GameModel {

        int x = 140;
        int y = 240;
        int roof = 60;
        int vely = 0;
        int velx = 0;
        int play;
        int gamestate;
        int blockstate;
        int blockvel = 5;
        int jumpstate = 1;
        int count = 1;
        int score;
        int block1 = 1600;
        int block2 = 2000;
        int block3 = 2500;
        int block4 = 2900;
        int block5 = 3300;
        int block6 = 3900;
        int block7 = 4350;
        int block8 = 4850;
        int block9 = 5350;
        int casm;
        int floor = 240;
        boolean fall = false;
    }

    public interface GameController {
        public void startGame();
        public void gameOver();
    }

    public class Game extends JPanel implements GameController {

        /**
         *
         */
        private static final long serialVersionUID = 1L;

        private GameModel model;
        private GameMenuScreen menu;
        private GameOverScreen gameOver;
        private GameScreen game;

        private CardLayout layout;

        public Game() {
            layout = new CardLayout();
            setLayout(layout);
            model = new GameModel();
            menu = new GameMenuScreen(model, this);
            gameOver = new GameOverScreen(model, this);
            game = new GameScreen(model, this);

            add(menu, "menu");
            add(gameOver, "gameOver");
            add(game, "game");

            layout.show(this, "menu");
        }

        @Override
        public void startGame() {
            layout.show(this, "game");
            game.start();
        }

        @Override
        public void gameOver() {
            game.stop();
            layout.show(this, "gameOver");
        }

    }

    public class GameOverScreen extends JPanel {

        private GameModel model;
        private GameController controller;

        public GameOverScreen(GameModel model, GameController controller) {
            this.model = model;
            this.controller = controller;
            setBackground(Color.DARK_GRAY);

            InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
            ActionMap actionMap = getActionMap();

            inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_R, 0), "start");
            actionMap.put("start", new AbstractAction() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    model.gamestate = 0;
                    model.block1 = 1600;
                    model.block2 = 2000;
                    model.block3 = 2500;
                    model.block4 = 2900;
                    model.block5 = 3300;
                    model.block6 = 3900;
                    model.block7 = 4350;
                    model.block8 = 4850;
                    model.block9 = 5350;
                    model.vely = 0;
                    model.velx = 0;
                    model.y = 240;
                    model.x = 140;
                    model.play = 0;
                    model.score = 0;
                    model.blockvel = 5;
                    model.fall = false;
                    controller.startGame();
                }
            });
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.setColor(Color.WHITE);
            g.drawString("game over, your Score was:" + model.score + " Metres", 500, 200);
            g.drawString("press r to play again", 500, 225);
        }

    }

    public class GameMenuScreen extends JPanel {

        private GameModel model;
        private GameController controller;

        public GameMenuScreen(GameModel model, GameController controller) {
            this.model = model;
            this.controller = controller;
            setBackground(Color.DARK_GRAY);

            InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
            ActionMap actionMap = getActionMap();

            inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0), "start");
            actionMap.put("start", new AbstractAction() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    model.gamestate = 1;
                    model.blockstate = 1;

                    controller.startGame();
                }
            });
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.drawString("press enter to play", 500, 200);
        }
    }

    public class GameScreen extends JPanel {

        private GameModel model;
        private GameController controller;

        private Timer timer;

        public GameScreen(GameModel model, GameController controller) {
            this.model = model;
            this.controller = controller;
            setBackground(Color.DARK_GRAY);

            InputMap inputMap = getInputMap(WHEN_IN_FOCUSED_WINDOW);
            ActionMap actionMap = getActionMap();

            inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, false), "jump");
            actionMap.put("jump", new AbstractAction() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    model.jumpstate = 2;
                    model.play = 1;
                }
            });
            inputMap.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0, true), "jump-release");
            actionMap.put("jump-release", new AbstractAction() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    model.jumpstate = 0; //??
                    model.play = 1;
                }
            });

            timer = new Timer(10, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    update();
                }
            });
        }

        public void start() {
            timer.restart();
            model.gamestate = 1;
        }

        public void stop() {
            timer.stop();
        }

        protected void update() {
            if (model.gamestate == 1 && model.play == 1) {
                model.block1 = model.block1 - model.blockvel;
                model.block2 = model.block2 - model.blockvel;
                model.block3 = model.block3 - model.blockvel;
                model.block4 = model.block4 - model.blockvel;
                model.block5 = model.block5 - model.blockvel;
                model.block6 = model.block6 - model.blockvel;
                model.block7 = model.block7 - model.blockvel;
                model.block8 = model.block8 - model.blockvel;
                model.block9 = model.block9 - model.blockvel;
                model.score = model.score + 1;
            }

            if (model.score == 2000) {
                model.blockvel = 7;
            }

            if (model.score == 5000) {
                model.blockvel = 9;
            }

            if (model.score == 7000) {
                model.blockvel = 11;
            }

            if (model.block1 < 0) {
                model.block1 = model.block9 + 500;
            }

            if (model.block2 < 0) {
                model.block2 = model.block1 + 500;
            }

            if (model.block3 < 0) {
                model.block3 = model.block2 + 500;
            }

            if (model.block4 < 0) {
                model.block4 = model.block3 + 500;
            }

            if (model.block5 < -200) {
                model.block5 = model.block4 + 500;
            }

            if (model.block6 < 0) {
                model.block6 = model.block5 + 500;
            }

            if (model.block7 < 0) {
                model.block7 = model.block6 + 500;
            }

            if (model.block8 < -200) {
                model.block8 = model.block7 + 500;
            }

            if (model.block9 < 0) {
                model.block9 = model.block8 + 500;
            }

            model.x = model.x + model.velx;
            model.y = model.y + model.vely;

            if (model.y < 60) {
                model.vely = 0;
            }

            //if( y > 240 ){
            //  vely = 0;
            // }
            if (model.jumpstate == 2) {
                model.vely = -6;
                if (model.y < model.roof) {
                    model.jumpstate = 3;
                    if (model.jumpstate == 3) {
                        model.vely = 6;
                        model.jumpstate = 4;
                    }
                }
            }
            if (model.y == model.floor && model.jumpstate == 4) {
                model.vely = 0;
                model.jumpstate = 1;
            }

            if ((model.x > model.block1 && model.x < model.block1 + 49) && model.y > 150) {
                model.gamestate = 2;
            }

            if ((model.x > model.block2 && model.x < model.block2 + 49) && model.y > 150) {
                model.gamestate = 2;
            }

            if ((model.x > model.block3 && model.x < model.block3 + 49) && model.y < 220) {
                model.gamestate = 2;
            }

            if ((model.x > model.block4 && model.x < model.block4 + 49) && model.y > 150) {
                model.gamestate = 2;
            }

            if ((model.x > model.block5 && model.x < model.block5 + 199) && (model.y == 240 && model.fall == false)) {
                model.fall = true;
                if (model.fall == true) {
                    model.vely = 6;
                }
            }

            if ((model.x > model.block6 && model.x < model.block6 + 49) && model.y > 150) {
                model.gamestate = 2;
            }

            if ((model.x > model.block7 && model.x < model.block7 + 49) && model.y < 220) {
                model.gamestate = 2;
            }

            if ((model.x > model.block8 && model.x < model.block8 + 199) && (model.y == 240 && model.fall == false)) {
                model.fall = true;
                if (model.fall == true) {
                    model.vely = 6;
                }
            }

            if ((model.x > model.block9 && model.x < model.block9 + 49) && model.y < 220) {
                model.gamestate = 2;
            }

            if (model.y > 500) {
                model.gamestate = 2;
            }

            if (model.gamestate == 2) {
                timer.stop();
                controller.gameOver();
            } else {
                repaint();
            }
        }

        @Override
        protected void paintComponent(Graphics g) {
            super.paintComponent(g);
            g.drawString("Score =" + model.score, 50, 20);
            setBackground(Color.gray);
            g.setColor(Color.cyan);
            g.fillRect(0, 270, 1500, 300);

            g.fillRect(model.block1, 150, 50, 150);
            g.fillRect(model.block2, 150, 50, 150);
            g.fillRect(model.block3, 0, 50, 220);
            g.fillRect(model.block4, 150, 50, 150);
            g.fillRect(model.block6, 150, 50, 150);
            g.fillRect(model.block7, 0, 50, 220);
            g.fillRect(model.block9, 0, 50, 220);
            g.setColor(Color.gray);
            g.fillRect(model.block5, 240, 200, 300);
            g.fillRect(model.block8, 240, 200, 300);

            g.setColor(Color.black);
            g.fillRect(model.x, model.y, 30, 30);
        }
    }
}

I run this code side by side from IDE and command line without issues

Remember

You don't control the painting process. There are lots of influences that can affect the painting process, the best you can do, in this case, is avoid common pitfalls and reduce any possible conflicts which might otherwise adversely effect the rendering process.

Also, Eclipse may be running your code with additional flags, which may help improve the performance, it would be a good idea to double check the settings

But it still doesn't work...

Yeah, it does that.

If it is still of significant concern to you, you may need to consider taking control of the painting process altogether.

To that end you should have a look at BufferStrategy and BufferCapabilities and the JavaDocs for BufferStrategy

Community
  • 1
  • 1
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366