1

The player is a panel, and it is getting removed, its position changed, and then re-added to another panel (which is what contains this method) which is drawn to the main frame. There are also a lot of other small panels containing a grass sprite being drawn to the primary panel as terrain tiles. I think the problem is that when I call revalidate(), it revalidates all those little panels as well. How can I solve this?

EDIT: I should mention that I am using RelativeLayout to position the players on the primary panel.

private class MainKeyAdapter extends KeyAdapter {

    @Override
    public void keyPressed(KeyEvent evt) {
        // TODO add your handling code here:
        if(AttentionedPlayer != null)
        {
            if (evt.getKeyCode() == KeyEvent.VK_UP) {
                AttentionedPlayer.Ypos -= 16;
            }
            if (evt.getKeyCode() == KeyEvent.VK_DOWN) {
                AttentionedPlayer.Ypos += 16;
            }
            if (evt.getKeyCode() == KeyEvent.VK_LEFT) {
                AttentionedPlayer.Xpos -= 16;
            }
            if (evt.getKeyCode() == KeyEvent.VK_RIGHT) {
                AttentionedPlayer.Xpos += 16;
            }
            remove(AttentionedPlayer);
            AttentionedPlayer.movePlayer();
            System.out.println("!!!!"+AttentionedPlayer.constraints.toString());
            add(AttentionedPlayer, AttentionedPlayer.constraints, AttentionedPlayer.Zpos);
            AttentionedPlayer.revalidate();
        }
    }

}
Jean-François Corbett
  • 37,420
  • 30
  • 139
  • 188
sunnydan
  • 9
  • 4
  • why are you *revalidating*? are you removing/adding components from the panels or frame? if not there is no need for it. BTW I would suggest use [KeyBindings](http://docs.oracle.com/javase/tutorial/uiswing/misc/keybinding.html) for Swing as `KeyListener` is AWT and has focus isssues – David Kroukamp Dec 09 '12 at 20:44
  • also you should not repaint the whole panel if only a small part changes. calculate what changes and use repaint(Rectangle r) – schippi Dec 09 '12 at 20:46
  • After your edit, in gaming I tend not to use Layouts, this is the few times where absolute positioning is best (Im talking about in the actual panel which draws the game). – David Kroukamp Dec 09 '12 at 20:51

2 Answers2

4

AttentionedPlayer.movePlayer(); seems to be an intensive operation, and you execute it from within EDT (the GUI thread). Instead, execute it from within a new thread or a SwingWorker.

Read this answer to know more about SwingWorker.

Community
  • 1
  • 1
Eng.Fouad
  • 115,165
  • 71
  • 313
  • 417
  • 1
    @DavidKroukamp That's the only non-swing-related method I can see that could cause some lag :) – Eng.Fouad Dec 09 '12 at 20:51
  • 1
    Now I get it.. +1. Though OP should also not really be using `revalidate()` if he has many panels this call will take long too – David Kroukamp Dec 09 '12 at 20:52
  • So to use SwingWorker, I essentially make a class that extends SwingWorker, override it doInBackground() function, and then just make it do whatever I want? – sunnydan Dec 09 '12 at 20:57
  • @sunnydan Yes, and then invoke `swingworkerinstance.execute()` inside `keyPressed()`. – Eng.Fouad Dec 09 '12 at 20:58
3

@Eng.Fouad had a good point +1 to him, though I personally have never needed this for that exact reason, but your move method might be very cpu intensive.

Just to show an example (expanding from my comments) using your JPanel game logic, if implemented correctly there would be no need for revalidate() on player move (via setLocation(..)) which IMO is what also could cause a great amount of lag especially if there are many components. As you will see my GamePanel extends JPanel and uses Null/Absolute Layout (but for good reason in gaming we want more control over the Layout).

Also used KeyBindings to show you their useage.

enter image description here

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.border.LineBorder;

public class GameLogic {

    public GameLogic() {
        initComponents();
    }

    private void initComponents() {

        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        Entity entity = new Entity("Player", 100, 100, 50, 50);//starting location 100,100 and width/height 50x50

        GamePanel gp = new GamePanel(300, 300);
        gp.addEntity(entity);

        setGamePanelKeyBindings(gp, entity);

        frame.add(gp);

        frame.pack();
        frame.setVisible(true);
    }

    private void setGamePanelKeyBindings(GamePanel gp, final Entity entity) {
        gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("D"), "D pressed");
        gp.getActionMap().put("D pressed", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                entity.move(Entity.RIGHT);
            }
        });

        gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("A"), "A pressed");
        gp.getActionMap().put("A pressed", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                entity.move(Entity.LEFT);
            }
        });

        gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("W"), "W pressed");
        gp.getActionMap().put("W pressed", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                entity.move(Entity.UP);
            }
        });

        gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("S"), "S pressed");
        gp.getActionMap().put("S pressed", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                entity.move(Entity.DOWN);
            }
        });
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new GameLogic();
            }
        });
    }
}

class Entity extends JPanel {

    private int width = 50, height = 50;
    private int speed = 5;
    public static final int UP = 1, DOWN = 2, LEFT = 3, RIGHT = 4;

    public Entity(String text, int x, int y, int width, int height) {
        this.width = width;
        this.height = height;
        add(new JLabel(text));
        setBorder(new LineBorder(Color.BLACK));
        setBounds(x, y, width, height);
    }

    public void move(int direction) {
        switch (direction) {
            case UP:
                setLocation(getX(), getY() - speed);
                break;
            case DOWN:
                setLocation(getX(), getY() + speed);
                break;
            case LEFT:
                setLocation(getX() - speed, getY());
                break;
            case RIGHT:
                setLocation(getX() + speed, getY());
                break;
        }

    }

    @Override
    public void setBounds(int x, int y, int w, int h) {
        super.setBounds(x, y, width, height);
    }

    @Override
    protected void paintComponent(Graphics grphcs) {
        super.paintComponent(grphcs);
        grphcs.setColor(Color.CYAN);
        grphcs.fillRect(0, 0, getWidth(), getHeight());
    }
}

class GamePanel extends JPanel {

    private int width, height;

    GamePanel(int w, int h) {
        setLayout(null);
        width = w;
        height = h;
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(width, height);
    }

    public void addEntity(Entity e) {
        add(e);
    }

    @Override
    protected void paintComponent(Graphics grphcs) {
        super.paintComponent(grphcs);
        grphcs.setColor(Color.GREEN);
        grphcs.fillRect(0, 0, getWidth(), getHeight());
    }
}

Sorry I couldn't stop myself

if you are interested here is a bit of an advanced version with gameloop that can be paused, frame rate etc can be set, 2 keys (like W and D may be pressed simultaneously thus causing JPanel to move diagonally):

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.util.ArrayList;
import javax.swing.AbstractAction;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.border.LineBorder;

public class GameLogic {

    public GameLogic() {
        initComponents();
    }
    final GamePanel gp = new GamePanel(500, 500);

    private void initComponents() {

        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        Entity entity = new Entity("Player", 100, 100, 100, 100);

        gp.addEntity(entity);

        setGamePanelKeyBindings(gp, entity);

        frame.add(gp);

        frame.pack();
        frame.setVisible(true);

        //start the game loop which will repaint the screen
        runGameLoop();
    }
    //Starts a new thread and runs the game loop in it.

    public void runGameLoop() {
        Thread loop = new Thread(new Runnable() {
            @Override
            public void run() {
                gp.running = true;
                gp.gameLoop();
            }
        });
        loop.start();
    }

    private void setGamePanelKeyBindings(GamePanel gp, final Entity entity) {
        gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("D"), "D pressed");
        gp.getActionMap().put("D pressed", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                entity.RIGHT = true;
            }
        });

        gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("A"), "A pressed");
        gp.getActionMap().put("A pressed", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                entity.LEFT = true;
            }
        });

        gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("W"), "W pressed");
        gp.getActionMap().put("W pressed", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                entity.UP = true;
            }
        });

        gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("S"), "S pressed");
        gp.getActionMap().put("S pressed", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                entity.DOWN = true;
            }
        });
        gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("released D"), "D released");
        gp.getActionMap().put("D released", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                entity.RIGHT = false;
            }
        });

        gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("released A"), "A released");
        gp.getActionMap().put("A released", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                entity.LEFT = false;
            }
        });

        gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("released W"), "W released");
        gp.getActionMap().put("W released", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                entity.UP = false;
            }
        });

        gp.getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(KeyStroke.getKeyStroke("released S"), "S released");
        gp.getActionMap().put("S released", new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                entity.DOWN = false;
            }
        });
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new GameLogic();
            }
        });
    }
}

class Entity extends JPanel {

    private int width = 50, height = 50;
    private int speed = 5;
    public boolean UP = false, DOWN = false, LEFT = false, RIGHT = false;

    public Entity(String text, int x, int y, int width, int height) {
        this.width = width;
        this.height = height;
        add(new JLabel(text));
        setBorder(new LineBorder(Color.BLACK));
        setBounds(x, y, width, height);
    }

    public void move() {
        if (UP) {
            setLocation(getX(), getY() - speed);
        }
        if (DOWN) {
            setLocation(getX(), getY() + speed);
        }
        if (LEFT) {
            setLocation(getX() - speed, getY());
        }
        if (RIGHT) {
            setLocation(getX() + speed, getY());
        }

    }

    @Override
    public void setBounds(int x, int y, int w, int h) {
        super.setBounds(x, y, width, height);
    }

    @Override
    protected void paintComponent(Graphics grphcs) {
        super.paintComponent(grphcs);
        grphcs.setColor(Color.CYAN);
        grphcs.fillRect(0, 0, getWidth(), getHeight());
    }
}

class GamePanel extends JPanel {

    private int width, height;
    private int frameCount = 0;
    private int fps = 0;
    public static boolean running = false, paused = false;
    final ArrayList<Entity> entities = new ArrayList<>();

    GamePanel(int w, int h) {
        setLayout(null);
        width = w;
        height = h;
    }

    @Override
    public Dimension getPreferredSize() {
        return new Dimension(width, height);
    }

    public void addEntity(Entity e) {
        add(e);
        entities.add(e);
    }

    @Override
    protected void paintComponent(Graphics grphcs) {
        super.paintComponent(grphcs);

        grphcs.setColor(Color.GREEN);
        grphcs.fillRect(0, 0, getWidth(), getHeight());

        grphcs.setColor(Color.BLACK);
        grphcs.drawString("FPS: " + fps, 5, 10);

        frameCount++;
    }

    //Only run this in another Thread!
    public void gameLoop() {
        //This value would probably be stored elsewhere.
        final double GAME_HERTZ = 30.0;
        //Calculate how many ns each frame should take for our target game hertz.
        final double TIME_BETWEEN_UPDATES = 1000000000 / GAME_HERTZ;
        //At the very most we will update the game this many times before a new render.
        //If you're worried about visual hitches more than perfect timing, set this to 1.
        final int MAX_UPDATES_BEFORE_RENDER = 5;
        //We will need the last update time.
        double lastUpdateTime = System.nanoTime();
        //Store the last time we rendered.
        double lastRenderTime = System.nanoTime();

        //If we are able to get as high as this FPS, don't render again.
        final double TARGET_FPS = 60;
        final double TARGET_TIME_BETWEEN_RENDERS = 1000000000 / TARGET_FPS;

        //Simple way of finding FPS.
        int lastSecondTime = (int) (lastUpdateTime / 1000000000);

        while (running) {
            double now = System.nanoTime();
            int updateCount = 0;

            if (!paused) {
                //Do as many game updates as we need to, potentially playing catchup.
                while (now - lastUpdateTime > TIME_BETWEEN_UPDATES && updateCount < MAX_UPDATES_BEFORE_RENDER) {
                    updateGame();
                    lastUpdateTime += TIME_BETWEEN_UPDATES;
                    updateCount++;
                }

                //If for some reason an update takes forever, we don't want to do an insane number of catchups.
                //If you were doing some sort of game that needed to keep EXACT time, you would get rid of this.
                if (now - lastUpdateTime > TIME_BETWEEN_UPDATES) {
                    lastUpdateTime = now - TIME_BETWEEN_UPDATES;
                }

                drawGame();
                lastRenderTime = now;

                //Update the frames we got.
                int thisSecond = (int) (lastUpdateTime / 1000000000);
                if (thisSecond > lastSecondTime) {
                    System.out.println("NEW SECOND " + thisSecond + " " + frameCount);
                    fps = frameCount;
                    frameCount = 0;
                    lastSecondTime = thisSecond;
                }

                //Yield until it has been at least the target time between renders. This saves the CPU from hogging.
                while (now - lastRenderTime < TARGET_TIME_BETWEEN_RENDERS && now - lastUpdateTime < TIME_BETWEEN_UPDATES) {
                    //allow the threading system to play threads that are waiting to run.
                    Thread.yield();

                    //This stops the app from consuming all your CPU. It makes this slightly less accurate, but is worth it.
                    //You can remove this line and it will still work (better), your CPU just climbs on certain OSes.
                    //FYI on some OS's this can cause pretty bad stuttering. Scroll down and have a look at different peoples' solutions to this.
                    //On my OS it does not unpuase the game if i take this away
                    try {
                        Thread.sleep(1);
                    } catch (Exception e) {
                    }

                    now = System.nanoTime();
                }
            }
        }
    }

    private void updateGame() {
        for (Entity e : entities) {
            e.move();
        }
    }

    private void drawGame() {

        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                repaint();
            }
        });
    }
}
David Kroukamp
  • 36,155
  • 13
  • 81
  • 138