Lag spike when moving player - java

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();
}
}
}

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.

#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.
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();
}
});
}
}

Related

The oval is not moving when I use thread in java implementing runnable Question

I have this code, that the oval shape should automatically move to the right/left/up/down (it depends on decision from the user) while implementing the runnable class. However it does not move. I would really appreciate any advice. Thank you in advance.
public class AnimationPanel extends JPanel implements Runnable{
public int getSpeed() {
return speed;
}
public void setSpeed(int speed) {
this.speed = speed;
}
public boolean isLeftToRight() {
return isLeftToRight;
}
public void setLeftToRight(boolean isLeftToRight) {
this.isLeftToRight = isLeftToRight;
}
private int x;
private int y;
private boolean isPaused = false;
private int width;
private int hwight;
private int speed=10;
private Thread animThread;
private boolean isLeftToRight = true;
public AnimationPanel() {
setBackground(Color.WHITE);
setDoubleBuffered(true);
x = 0;
y = 0;
animThread = new Thread(this);
animThread.start();
}
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.BLUE);
g2.fillOval(x, y,20,20);
}
public void movement() {
hwight = this.getHeight();
width = this.getWidth();
if(isLeftToRight) {
if(x<width) {
x=+10;
} else if(x>=0){
x=-10;
}
} else {
if(y<hwight) {
y=+10;
} else if(y>=0){
y=-10;
}
}
}
#Override
public void run() {
while(true) {
if (!isPaused)
{
movement();
repaint();
}
try {
Thread.sleep(speed);
} catch(InterruptedException e) {
JOptionPane.showMessageDialog(null, "Interrupted");
}
}
}
}
public class AppFrame extends JFrame implements ActionListener{
private AnimationPanel anim;
private JButton slowerButton;
private JButton fasterButton;
private JButton upDownButton;
private JButton leftRightButton;
private JPanel buttonsPanel;
private int height = 500;
private int width = 500;
public AppFrame() {
this.setTitle("Circle");
this.setSize(height,width);
this.setResizable(false);
initGui();
this.setLocationRelativeTo(null);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
}
private void initGui() {
buttonsPanel = new JPanel();
this.setLayout(new BorderLayout());
buttonsPanel.setLayout(new FlowLayout());
buttonsPanel.add(slowerButton = new JButton("Slower"));
buttonsPanel.add(fasterButton = new JButton("Faste"));
buttonsPanel.add(upDownButton = new JButton("up and down"));
buttonsPanel.add(leftRightButton = new JButton("left to right"));
slowerButton.addActionListener(this);
fasterButton.addActionListener(this);
upDownButton.addActionListener(this);
leftRightButton.addActionListener(this);
anim = new AnimationPanel();
this.add(buttonsPanel, BorderLayout.PAGE_START);
this.add(anim);
}
#Override
public void actionPerformed(ActionEvent e) {
Object zrodlo = e.getSource();
if(slowerButton == zrodlo) {
anim.setSpeed(anim.getSpeed()*2);
} else if(fasterButton == zrodlo) {
anim.setSpeed(anim.getSpeed()/2);
} else if(upDownButton == zrodlo) {
anim.setLeftToRight(false);
} else if(leftRightButton == zrodlo) {
anim.setLeftToRight(true);
}
}
}
public class Runner {
public static void main(String[] args) {
AppFrame app =new AppFrame();
app.setVisible(true);
}
}
What's going wrong...
if (x < width) {
x = +10;
} else if (x >= 0) {
x = -10;
}
So, the above code is simply assigning either -10 or +10 to the x variable, it never increments/decrements the value. Use += and -= instead
if (x < width) {
x += 10;
} else if (x >= 0) {
x -= 10;
}
This will solve the immediate issue, but create a new one. It would be better to have a simple delta which was either positive or negative and is then simply applied to the variable
x += delta; // +/- speed
if (x + 20 >= width) {
x = width - 20;
delta *= -1
} ...
Additional fixes
Swing is not thread, you should not be using threads to change the state of the UI. Start by having a look at Concurrency in Swing and How to Use Swing Timers for more details.
You should also have a look at Painting in AWT and Swing and Performing Custom Painting as you should be preferring paintComponent over paint
The following is a rewrite of your example using a Swing Timer
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.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JToggleButton;
import javax.swing.Timer;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
protected class TestPane extends JPanel {
private AnimationPanel animationPane;
public TestPane() {
setLayout(new BorderLayout());
animationPane = new AnimationPanel();
add(animationPane);
JToggleButton pauseButton = new JToggleButton("Pause");
pauseButton.setSelected(true);
pauseButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
animationPane.setPaused(pauseButton.isSelected());
}
});
JButton fasterButton = new JButton("Faster");
fasterButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
animationPane.setSpeed(animationPane.getSpeed() * 2);
}
});
JButton slowerButton = new JButton("Slower");
slowerButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
animationPane.setSpeed(animationPane.getSpeed() / 2);
}
});
JToggleButton horizontalButton = new JToggleButton("Horizontal");
JToggleButton verticalButton = new JToggleButton("Vertical");
horizontalButton.setSelected(true);
ButtonGroup bg = new ButtonGroup();
bg.add(horizontalButton);
bg.add(verticalButton);
horizontalButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
animationPane.setLeftToRight(horizontalButton.isSelected());
}
});
verticalButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
animationPane.setLeftToRight(!verticalButton.isSelected());
}
});
JPanel actionPane = new JPanel();
actionPane.add(pauseButton);
actionPane.add(slowerButton);
actionPane.add(fasterButton);
actionPane.add(horizontalButton);
actionPane.add(verticalButton);
add(actionPane, BorderLayout.SOUTH);
}
}
public class AnimationPanel extends JPanel {
public int getSpeed() {
return speed;
}
public void setSpeed(int speed) {
this.speed = speed;
}
public boolean isLeftToRight() {
return isLeftToRight;
}
public void setLeftToRight(boolean isLeftToRight) {
this.isLeftToRight = isLeftToRight;
}
private boolean paused = true;
private int speed = 10;
private boolean isLeftToRight = true;
private Point origin = new Point(190, 190);
private Timer timer;
public AnimationPanel() {
timer = new Timer(16, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
movement();
repaint();
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.BLUE);
g2d.fillOval(origin.x, origin.y, 20, 20);
g2d.dispose();
}
public boolean isPaused() {
return paused;
}
public void setPaused(boolean paused) {
this.paused = paused;
if (paused) {
timer.stop();
} else {
timer.start();
}
}
public void movement() {
int height = this.getHeight();
int width = this.getWidth();
if (isLeftToRight) {
origin.x += speed;
if (origin.x + 20 >= width) {
speed *= -1;
origin.x = width - 20;
} else if (origin.x <= 0) {
speed *= -1;
origin.x = 0;
}
} else {
origin.y += speed;
if (origin.y + 20 >= height) {
speed *= -1;
origin.y = height - 20;
} else if (origin.y <= 0) {
speed *= -1;
origin.y = 0;
}
}
}
}
}
A "different" approach
I don't like delta based animations, I think they are shorted sighted and they generally produce bad results. Where possible, I prefer to make use of time based animations. That is, based on a given time, move the object over a given range.
When done right, this is really flexible. It allows the system to drop frames automatically without the animation "stalling" and generally produces nicer animation which is generally easier to manage and maintained.
This does, however, introduce more complexity, but if you spend the time to generalise the workflows, they can be re-used relatively easily.
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.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import java.time.Duration;
import java.time.Instant;
import javax.swing.ButtonGroup;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JToggleButton;
import javax.swing.Timer;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
protected class TestPane extends JPanel {
private AnimationPanel animationPane;
public TestPane() {
setLayout(new BorderLayout());
animationPane = new AnimationPanel();
add(animationPane);
JToggleButton pauseButton = new JToggleButton("Run");
pauseButton.setSelected(true);
pauseButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (pauseButton.isSelected()) {
pauseButton.setText("Run");
} else {
pauseButton.setText("Pause");
}
animationPane.setPaused(pauseButton.isSelected());
}
});
// JButton fasterButton = new JButton("Faster");
// fasterButton.addActionListener(new ActionListener() {
// #Override
// public void actionPerformed(ActionEvent e) {
// animationPane.setSpeed(animationPane.getSpeed() * 2);
// }
// });
// JButton slowerButton = new JButton("Slower");
// slowerButton.addActionListener(new ActionListener() {
// #Override
// public void actionPerformed(ActionEvent e) {
// animationPane.setSpeed(animationPane.getSpeed() / 2);
// }
// });
JToggleButton horizontalButton = new JToggleButton("Horizontal");
JToggleButton verticalButton = new JToggleButton("Vertical");
horizontalButton.setSelected(true);
ButtonGroup bg = new ButtonGroup();
bg.add(horizontalButton);
bg.add(verticalButton);
// horizontalButton.addActionListener(new ActionListener() {
// #Override
// public void actionPerformed(ActionEvent e) {
// animationPane.setLeftToRight(horizontalButton.isSelected());
// }
// });
//
// verticalButton.addActionListener(new ActionListener() {
// #Override
// public void actionPerformed(ActionEvent e) {
// animationPane.setLeftToRight(!verticalButton.isSelected());
// }
// });
JPanel actionPane = new JPanel();
actionPane.add(pauseButton);
// actionPane.add(slowerButton);
// actionPane.add(fasterButton);
// actionPane.add(horizontalButton);
// actionPane.add(verticalButton);
add(actionPane, BorderLayout.SOUTH);
}
}
public class AnimationPanel extends JPanel {
public enum Direction {
VERTICAL, HORIZONTAL
}
private Direction direction = Direction.HORIZONTAL;
private Point2D origin = new Point2D.Double(200, 200);
private Animator animator;
private Range<Double> range;
private Duration duration = Duration.ofSeconds(5);
private Ellipse2D dot = new Ellipse2D.Double(0, 0, 20, 20);
public AnimationPanel() {
animator = new Animator(new Animator.Observer() {
#Override
public void animatorDidTick(Animator animator, double progress) {
double nextValue = range.valueAt(progress);
if (direction == Direction.HORIZONTAL) {
origin.setLocation(nextValue, origin.getY());
}
repaint();
}
#Override
public void animatorDidComplete(Animator animator) {
double targetPoint = range.getTo();
if (direction == Direction.HORIZONTAL) {
range = getDotHorizontalRange();
if (targetPoint != range.getFrom()) {
range.reverse();
}
}
animator.setDuration(duration);
resume();
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
g2d.setColor(Color.BLUE);
g2d.drawRect(0, 0, getWidth() - 1, getHeight() - 1);
g2d.draw(new Line2D.Double(0, getHeight() / 2, getWidth(), getHeight() / 2));
g2d.draw(new Line2D.Double(getWidth() / 2, 0, getWidth() / 2, getHeight()));
g2d.translate(origin.getX() - (dot.getWidth() / 2d), origin.getY() - (dot.getHeight() / 2d));
g2d.fill(dot);
g2d.dispose();
}
protected Range<Double> getDotHorizontalRange() {
return new DoubleRange(dot.getWidth() / 2, getWidth() - (dot.getWidth() / 2));
}
protected double getHorizontalRangeDistance() {
return ((DoubleRange)getDotHorizontalRange()).getDistance();
}
public void setPaused(boolean paused) {
if (paused) {
animator.pause();
} else {
if (range == null) {
initialiseRange();
}
animator.resume();
}
}
protected void resume() {
if (range == null) {
// Try and force a restart...
setPaused(false);
}
animator.resume();
}
protected void initialiseRange() {
if (direction == Direction.HORIZONTAL) {
double currentX = origin.getX();
// Assume a positive intial direction
double avaliableRange = Math.abs(getHorizontalRangeDistance());
double distance = avaliableRange - currentX;
int remainingTime = (int)(duration.toMillis() * (distance / avaliableRange));
animator.setDuration(Duration.ofMillis(remainingTime));
range = new DoubleRange((double)currentX, getDotHorizontalRange().getTo());
}
}
}
public abstract class Range<T> {
private T from;
private T to;
public Range(T from, T to) {
this.from = from;
this.to = to;
}
public T getFrom() {
return from;
}
public T getTo() {
return to;
}
#Override
public String toString() {
return "From " + getFrom() + " to " + getTo();
}
public abstract T valueAt(double progress);
public void reverse() {
T nextFrom = to;
to = from;
from = nextFrom;
}
}
public class DoubleRange extends Range<Double> {
public DoubleRange(Double from, Double to) {
super(from, to);
}
public Double getDistance() {
return getTo() - getFrom();
}
#Override
public Double valueAt(double progress) {
double distance = getDistance();
double value = distance * progress;
value += getFrom();
return value;
}
}
public class Animator {
public enum State {
STOP, PAUSE, RUN
}
public interface Observer {
public void animatorDidTick(Animator animator, double progress);
public void animatorDidComplete(Animator animator);
}
private Duration duration = Duration.ofSeconds(5);
// Used to manage pause support. This will be
// added onto the "live" runtime when the
// animator is running
private Duration previousRuntime = Duration.ZERO;
private Instant epoch;
private Observer observer;
private State state = State.STOP;
// This is actually used to manage the "ticks"
private Timer ticker = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (epoch == null) {
epoch = Instant.now();
}
double progress = getProgressAtCurrentTime();
observer.animatorDidTick(Animator.this, Math.max(0, Math.min(1.0, progress)));
if (progress >= 1.0) {
progress = 1.0;
stop();
observer.animatorDidComplete(Animator.this);
}
}
});
public Animator(Observer observer) {
this.observer = observer;
}
public void setDuration(Duration duration) {
this.duration = duration;
}
public boolean isPaused() {
return state == State.PAUSE;
}
public boolean isRunning() {
return state == State.RUN;
}
public boolean isStopped() {
return state == State.STOP;
}
public void pause() {
ticker.stop();
if (epoch != null) {
Duration runtime = Duration.between(epoch, Instant.now());
previousRuntime = previousRuntime.plus(runtime);
state = State.PAUSE;
}
epoch = null;
}
public void resume() {
state = State.RUN;
ticker.start();
}
protected double getProgressAtCurrentTime() {
Duration runtime = Duration.ZERO;
if (epoch != null) {
// The delta time between when we started and now
runtime = Duration.between(epoch, Instant.now());
}
// Plus any additonal time which was recored
runtime = runtime.plus(previousRuntime);
return runtime.toMillis() / (double) duration.toMillis();
}
// This is for internal reset purposes
protected void stop() {
ticker.stop();
state = State.STOP;
previousRuntime = Duration.ZERO;
epoch = null;
}
}
}
The above example makes use of a concept of "normalised" time. That is, any given animation transitions over a time range of 0-1. This makes it incredibly easy to change the speed. Want to to go faster? Decrease the duration. Slower? Increase the duration. Everything else is done by simply calculating the required properties against a "from" and "to" state and the current "normalised time" value.
For example, look at the animation above. The dot starts at the halfway point, but the time it takes to get to the other side is no different then the time it takes to return ALL the way to far side (the speed doesn't change), this is because, the initial duration is calculated based on a delta of the whole range and the current position (that is, the initial duration is 50% of the desired duration).
The above example also allows you to "pause" the running animation and when it resumes, it will continue as if nothing happened.
Animation, I mean, really good animation, is complicated. Don't believe me, take a look at move dot between 2 points jframe (which I've spent the last few days building out as personal projects to run in Java and MacOS, because apparently I don't have a live) and How can I implement easing functions with a thread which takes a deep dive into just one aspect of the animation theory and don't even get me started on "time line" animation.

Flickering While Resizing Transparent JWindow

I'm trying to make a transparent window with Java Swing, and I have created the window. Problem is when I resize the window, it flickers. I tried changing the background to an opaque color instead. That fixed the problem, But I want the window to be transparent. I have also tried
Toolkit.getDefaultToolkit().setDynamicLayout(true);,
Toolkit.getDefaultToolkit().getDesktopProperty("awt.dynamicLayoutSupported");,
System.setProperty("sun.awt.noerasebackground", "true");,
But with no avail. I've tried JWindow.setBounds instead of JWindow.setSize, but that also had no effect.
Here is the code I use to produce the window
import java.awt.Color;
import java.awt.Graphics;
import java.awt.MouseInfo;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import javax.swing.JPanel;
import javax.swing.JWindow;
public class GlassWindow extends JPanel {
// RIGHT CLICK TO CLOSE
private static final long serialVersionUID = 1L;
private JWindow jwindow = new JWindow();
private boolean programCalledRender, shouldClose;
private int edges = 8;
private Color background = new Color(255, 100, 0, 100);
public GlassWindow() {
int width = 1000, height = 750;
this.setOpaque(false);
this.setSize(width, height);
jwindow.setSize(width, height);
jwindow.setBackground(new Color(0, 0, 0, 0));
jwindow.setContentPane(this);
jwindow.setLocationRelativeTo(null);
jwindow.setVisible(true);
jwindow.addMouseListener(new MouseListener() {
public void mouseClicked(MouseEvent e) {
if(e.getButton() == MouseEvent.BUTTON3) System.exit(0);
}
public void mouseEntered(MouseEvent e) {}
public void mouseExited(MouseEvent e) {}
public void mousePressed(MouseEvent e) {}
public void mouseReleased(MouseEvent e) {}
});
new Thread(() -> run()).start();
}
public void render() {
programCalledRender = true;
this.repaint();
}
private void run() {
int setTPS = 20, setFPS = 60;
long lastTime = System.nanoTime();
double delta = 0, frameDelta = 0;
while(true) {
if(shouldClose) break;
long now = System.nanoTime();
delta += (now-lastTime)/(1000000000/(double)setTPS);
frameDelta += (now-lastTime)/(1000000000/(double)setFPS);
lastTime = now;
while(delta > 0) {
tick();
delta--;
}
while(frameDelta > 0) {
render();
frameDelta--;
}
}
}
private void tick() {
}
#Override
public void paintComponent(Graphics g) {
if(!programCalledRender) return;
super.paintComponent(g);
int newWidth = MouseInfo.getPointerInfo().getLocation().x+20-jwindow.getX();
if(newWidth != jwindow.getWidth()) {
jwindow.setSize(newWidth, jwindow.getHeight());
}
if(background != null) {
g.setColor(background);
g.fillRect(edges, edges, jwindow.getWidth()-edges*2, jwindow.getHeight()-edges*2);
}
g.setColor(new Color(0, 0, 0, 100));
for(int i = 0; i <= edges; i++) {
g.drawRect(i, i, jwindow.getWidth()-i*2, jwindow.getHeight()-i*2);
}
}
public static void main(String[] args) {
new GlassWindow();
}
}
How can I prevent flickering when the JWindow is being resized?
Any help is appreciated.
As suggested by #camickr I should not resize the window in the render method.
So to stop the flickering I remove
int newWidth = MouseInfo.getPointerInfo().getLocation().x+20-jwindow.getX();
if(newWidth != jwindow.getWidth()) {
jwindow.setSize(newWidth, jwindow.getHeight());
}
from the paintComponent(Graphics g) method. And add
jwindow.addMouseMotionListener(new MouseMotionListener() {
public void mouseDragged(MouseEvent e) {}
public void mouseMoved(MouseEvent e) {
int newWidth = MouseInfo.getPointerInfo().getLocation().x+20-jwindow.getX();
if(newWidth != jwindow.getWidth()) {
jwindow.setSize(newWidth, jwindow.getHeight());
}
}
});
to the constructor GlassWindow().

Eclipse is not running Java method: public void paint (Graphics g)

I'm new to Eclipse, recently swapped from Bluej which ran my codes reliably. In Eclipse, it sometimes runs and sometimes just doesn't run the paint method and I'm not sure why. The same code was running this morning and now it decides to not run and I'm not sure what to do.
Main method:
import java.awt.Container;
import java.awt.Dimension;
import javax.swing.JFrame;
import javax.swing.JPanel;
import Asset.Paddle;
import Asset.Puck;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseEvent;
import java.awt.BorderLayout;
public class setup implements KeyListener, MouseListener, MouseMotionListener, Runnable {
int width = 100;
int height = 100;
int scale = 8;
public static setup setup;
JFrame frame;
JPanel main;
Graphic graphic;
Puck puck;
Paddle paddle1,paddle2;
boolean running, up = true, up2 = true;
boolean menu = false, b1, b2, b3;
int winSize;
public setup() {
puck = new Puck((width*scale)/2,(width*scale)/2,20,20);
paddle1 = new Paddle(width*scale/8-20,height*scale/2,20,100);
paddle2 = new Paddle(width*scale/8*7,height*scale/2,20,100);
frame();
}
public void frame() { //Frame setup
frame = new JFrame("Pong");
frame.setSize(width * scale,height * scale);
frame.setLayout(new BorderLayout());
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container c1 = frame.getContentPane();
Dimension winSize = frame.getSize();
System.out.println(winSize);
graphic = new Graphic(puck,paddle1,paddle2);
graphic.addKeyListener(this);
graphic.addMouseListener(this);
graphic.addMouseMotionListener(this);
main = new JPanel();
main.setLayout(new BorderLayout());
main.setSize(width * scale,height * scale);
main.add(graphic,BorderLayout.CENTER);
start();
c1.add(main);
graphic.requestFocus();
}
public void start() { //running = true
new Thread(this).start();
running = true;
menu = true;
RENDER();
}
public void stop() {
running = false;
}
public void run() { //Game
while(running == true) {
try {
Thread.sleep(10);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
if(puck.getY() < 0 || puck.getY() > height * scale) {
puck.reverseY();
}
paddle1.run();
paddle2.run();
puck.run();
RENDER();
}
}
public void RENDER() {
graphic.UPDATEPADDLE(paddle1,paddle2);
graphic.UPDATEPUCK(puck);
graphic.repaint();
}
public void keyPressed(KeyEvent evt) {
if(evt.getKeyCode() == 81) { // Q
paddle1.setYVel(-2);
up = true;
}
if(evt.getKeyCode() == 65) { // A
paddle1.setYVel(2);
up = false;
}
if(evt.getKeyCode() == 80) { // P
paddle2.setYVel(-2);
up2 = true;
}
if(evt.getKeyCode() == 76) {
paddle2.setYVel(2);
up2 = false;
}
}
public void keyReleased(KeyEvent evt) {
if(evt.getKeyCode() == 81 && up ) { // Q
paddle1.setYVel(0);
}
if(evt.getKeyCode() == 65 && !up) { // A
paddle1.setYVel(0);
}
if(evt.getKeyCode() == 80 && up2) { // P
paddle2.setYVel(0);
}
if(evt.getKeyCode() == 76 && !up2) { // L
paddle2.setYVel(0);
}
}
public void keyTyped(KeyEvent evt) {}
public void mouseClicked(MouseEvent e) {
// if(e.getX() > 375 && e.getX() < 375 + 200 && e.getY() > 400 && e.getY() < 400 + 50) {
// menu = false;
// System.out.println("clicked");
// graphic.UPDATEBUTTON(b1,b2,b3);
// graphic.UPDATEMENU(menu);
// start();
// graphic.repaint();
// }
}
public void mouseEntered(MouseEvent e) {
}
public void mouseExited(MouseEvent e) {
}
public void mousePressed(MouseEvent e) {
}
public void mouseReleased(MouseEvent e) {
}
public static void main(String[] args) {
setup = new setup();
}
public void mouseDragged(MouseEvent e) {
}
public void mouseMoved(MouseEvent e) {
if(e.getX() > 375 && e.getX() < 375 + 200 && e.getY() > 400 && e.getY() < 400 + 50) {
b1 = true;
graphic.UPDATEBUTTON(b1,b2,b3);
graphic.repaint();
}
else {
b1 = false;
graphic.UPDATEBUTTON(b1,b2,b3);
graphic.repaint();
}
}
}
Paddle:
package Asset;
public class Paddle {
double x, y, yVel, h, w;
public Paddle(double xx, double yy, int width, int height) {
x = xx;
y = yy;
h = height;
w = width;
}
public int getX() {
return (int)x;
}
public int getY() {
return (int)y;
}
public int getH() {
return (int)h;
}
public int getW() {
return (int)w;
}
public void setYVel(int yVelocity) {
yVel = yVelocity;
}
public void run() {
y += yVel;
if(y < 0) {
yVel = 0;
y = 0;
}
}
}
Puck:
package Asset;
public class Puck {
double x,y,w,h;
double xVel = 0;
double yVel = 3;
public Puck(double xx, double yy,int width,int height) {
x = xx;
y = yy;
w = width;
h = height;
x++;
}
public void reverseY() {
yVel *= -1;
}
public void reverseX() {
xVel *= -1;
}
public int getX() {
return (int)x;
}
public int getY() {
return (int)y;
}
public int getW() {
return (int)w;
}
public int getH() {
return (int)h;
}
public void run() {
x += xVel;
y += yVel;
}
}
Setup:
import java.awt.*;
import javax.swing.*;
import Asset.Paddle;
import Asset.Puck;
public class Graphic extends JPanel {
private static final long serialVersionUID = 2273791975624707192L;
Puck ppuck;
Paddle ppaddle1,ppaddle2;
boolean mmenu = true;
boolean bb1,bb2,bb3;
public Graphic(Puck puck,Paddle paddle1,Paddle paddle2) {
ppuck = puck;
ppaddle1 = paddle1;
ppaddle2 = paddle2;
}
public void UPDATEMENU(boolean menu) {
mmenu = menu;
}
public void UPDATEPADDLE(Paddle paddle1, Paddle paddle2) {
ppaddle1 = paddle1;
ppaddle2 = paddle2;
}
public void UPDATEPUCK(Puck puck) {
ppuck = puck;
}
public void UPDATEBUTTON(boolean b1,boolean b2, boolean b3) {
bb1 = b1;
bb2 = b2;
bb3 = b3;
}
public void paint (Graphics g) {
super.paint(g);
g.setColor(Color.black);
g.fillRect(0, 0, 1000, 1000);
if (mmenu) { //menu
if(bb1) {
g.setColor(Color.blue);
g.setFont(new Font("TimesRoman", Font.PLAIN, 50));
g.drawString("START", 390, 440);
}
else {
g.setColor(Color.white);
g.setFont(new Font("TimesRoman", Font.PLAIN, 50));
g.drawString("START", 390, 440);
}
}
else {
g.setColor(Color.white);
g.fillOval(ppuck.getX(), ppuck.getY(), ppuck.getW(), ppuck.getH());
g.fillRect(ppaddle1.getX(), ppaddle1.getY(), ppaddle1.getW(), ppaddle1.getH());
g.fillRect(ppaddle2.getX(), ppaddle2.getY(), ppaddle2.getW(), ppaddle2.getH());
}
}
}
So, two "basic" problems...
One, if you modify the UI after the frame is visible, you must call revalidate and repaint too trigger a layout and paint pass. A simpler solution, in your case, would be to call setVisible AFTER you've established the UI
public void frame() { //Frame setup
frame = new JFrame("Pong");
frame.setSize(width * scale, height * scale);
frame.setLayout(new BorderLayout());
frame.setLocationRelativeTo(null);
//frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Container c1 = frame.getContentPane();
//...
start();
c1.add(main);
graphic.requestFocus();
frame.setVisible(true);
}
Second...
This one's a little more complicated, but, you start your Thread and then update the state which it relies on to keep running
public void start() { //running = true
new Thread(this).start();
running = true;
menu = true;
RENDER();
}
While very, very unlikely, it's possible that the thread will inspect the state before you change it ... or because of the way the memory model works, won't see the change.
Better to set it before hand...
public void start() { //running = true
running = true;
new Thread(this).start();
menu = true;
RENDER();
}
You should also consider making it volatile
Having said that...
You're going around it all the wrong way.
To start with, don't try and do all the rendering for all the states in the single view, instead, use seperate views to different states (such as the start screen and the game screen).
You could then make use of CardLayout or simply overlay the containers onto of each other when you want to switch between them.
Next, you should avoid using KeyListener, it's troublesome at the best of times. Instead, make us of the key bindings API, then you won't need to post another question about why KeyListener has stopped working.
Next, Swing is single threaded and not thread safe. This means you should not be performing blocking or long running operations within the context of the Event Dispatching Thread or updating the UI or a state the UI depends on from outside of it.
Take a look at Concurrency in Swing for more details.
The simple solution in this case is probably to use a Swing Timer, see How to use Swing Timers for more details.
I would, personally, make the "game" panel responsible for setting up the input and rendering management, but that's me.

Smooth out Java paint animations

I would like to make my app draw moving image a little smoother than how it is currently drawing them. I am not sure what to do to do that.
This is what my main game Thread looks like:
#Override
public void run(){
int delay = 500; //milliseconds
ActionListener taskPerformer = new ActionListener(){
#Override
public void actionPerformed(ActionEvent evt){
Car car = new Car();
int speed = (int)(3 + Math.floor(Math.random() * (6 - 3)));
car.setSpeed(speed);
MainLoop.this.gameObjects.vehicles.add(car.create("/Media/Graphics/blueCar.png", width - 20, 78));
car.driveTo(0, 78);
}
};
new Timer(delay, taskPerformer).start();
try{
while(true){
this.repaint();
for(GameObject go : this.gameObjects.vehicles){
// loops through objects to move them
Vehicle vh = (Vehicle) go;
this.moveVehicle(vh);
if(vh.getX() <= vh.getDestX()){
vh.markForDeletion(true);
}
}
this.gameObjects.destroyVehicles();
Thread.sleep(1);
}
}catch(Exception e){
e.printStackTrace();
}
}
This is a method that calculates the items next x/y position
protected void moveVehicle(Vehicle vh){
int cx = vh.getX();
int dx = vh.getDestX();
int cy = vh.getY();
int dy = vh.getDestY();
// move along x axis
// getMaxSpeed() = Number between 3 and 6
if(cx > dx && vh.movingX() == -1){
vh.setX(cx - vh.getMaxSpeed());
}else if(cx < dx && vh.movingX() == 1){
vh.setX(cx + vh.getMaxSpeed());
}else{
vh.setX(dx);
}
// move along y axis
// getMaxSpeed() = Number between 3 and 6
if(cy > dy && vh.movingY() == -1){
vh.setY(cy - vh.getMaxSpeed());
}else if(cy < dy && vh.movingY() == 1){
vh.setY(cy + vh.getMaxSpeed());
}else{
vh.setY(dy);
}
}
This is my paint method:
#Override
public void paintComponent(Graphics graphics){
super.paintComponent(graphics);
Graphics2D g = (Graphics2D) graphics;
for(GameObject go : gameObjects.vehicles){
g.drawImage(go.getSprite(), go.getX(), go.getY(), this);
}
}
This is possibly more info than needed, but I would like to know, what should I do to make items move from left -> right top -> bottom as smoothly as possible, without much of a performance loss?
Edit: Requested sscce:
package sscce;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Sscce extends JPanel implements Runnable{
ArrayList<Square> squares = new ArrayList<>();
public Sscce(){
JFrame frame = new JFrame();
frame.setSize(500, 500);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
frame.add(this);
Thread t = new Thread(this);
t.start();
}
public static void main(String[] args){
new Sscce();
}
#Override
public void run(){
int delay = 500; //milliseconds
ActionListener taskPerformer = new ActionListener(){
#Override
public void actionPerformed(ActionEvent evt){
Square squ = new Square();
Sscce.this.squares.add(squ);
squ.moveTo(0);
}
};
new Timer(delay, taskPerformer).start();
while(true){
try{
for(Square s : this.squares){
int objX = s.getX();
int desX = s.getDestX();
if(objX <= desX){
System.out.println("removing");
this.squares.remove(s);
}else{
s.setX(s.getX() - 10);
}
}
this.repaint();
Thread.sleep(30);
}catch(Exception e){
}
}
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
for(Square s : squares){
g.setColor(Color.blue);
g.fillRect(s.getX(), s.getY(), 50, 50);
}
}
}
class Square{
public int x = 0, y = 0, destX = 0;
public Square(){
this.x = 400;
this.y = 100;
}
public void moveTo(int destX){
this.destX = destX;
}
public int getX(){
return this.x;
}
public int getDestX(){
return this.destX;
}
public void setX(int x){
this.x = x;
}
public int getY(){
return this.y;
}
}
First note, for some reason, I had issues with the JPanel implementing Runnable when running it under MacOS - I have no idea why, but that's why I moved it out.
It is possible for your squares to be update while it is been used to paint which will cause an exception (also, removing elements from a list while you're iterating it isn't a good idea either ;))
Instead, I have two lists. I have a model, which the list can modify and then the paint list which the paint method can use. This allows the thread to modifiy the model, while a paint process is underway.
To prevent any clash, I've added in a lock, which prevents one thread from modifying/accessing the paint list, while another thread has it locked.
Now. Down to the real problem. The main issue you're having isn't the amount of time between the updates, but the distance you are moving. Reduce the distance (to make it slower) and standarise the updates.
Most people won't notice anything much over 25fps, so trying to do much more then that is just wasting CPU cycles and starving the repaint manager, preventing it from actually updating the screen.
It's a balancing act to be sure...
import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class TestAnimation11 extends JPanel {
private ArrayList<Square> squares = new ArrayList<>();
private ReentrantLock lock;
public TestAnimation11() {
lock = new ReentrantLock();
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame();
frame.setSize(500, 500);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
frame.add(TestAnimation11.this);
Thread t = new Thread(new UpdateEngine());
t.start();
}
});
}
public static void main(String[] args) {
new TestAnimation11();
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Square[] paint = null;
lock.lock();
try {
paint = squares.toArray(new Square[squares.size()]);
} finally {
lock.unlock();
}
for (Square s : paint) {
g.setColor(Color.blue);
g.fillRect(s.getX(), s.getY(), 50, 50);
}
}
public class UpdateEngine implements Runnable {
private List<Square> model = new ArrayList<>(squares);
#Override
public void run() {
int ticks = 0;
List<Square> dispose = new ArrayList<>(25);
while (true) {
ticks++;
dispose.clear();
for (Square s : model) {
int objX = s.getX();
int desX = s.getDestX();
if (objX <= desX) {
dispose.add(s);
} else {
s.setX(s.getX() - 2);
}
}
model.removeAll(dispose);
if (ticks == 11) {
Square sqr = new Square();
sqr.moveTo(0);
model.add(sqr);
} else if (ticks >= 25) {
ticks = 0;
}
lock.lock();
try {
squares.clear();
squares.addAll(model);
} finally {
lock.unlock();
}
repaint();
try {
Thread.sleep(40);
} catch (Exception e) {
}
}
}
}
class Square {
public int x = 0, y = 0, destX = 0;
public Square() {
this.x = 400;
this.y = 100;
}
public void moveTo(int destX) {
this.destX = destX;
}
public int getX() {
return this.x;
}
public int getDestX() {
return this.destX;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return this.y;
}
}
}

Object issues in Java

I'm attempting to implement the dragging functionality of shapes on a canvas. My Shape class inherits from JPanel.
Absolutely nothing happens when I click on a shape, drag it and let go of the mouse button. It just remains where it was originally. Any ideas?
You need a few basic things:
A field for the shape itself (you already had)
Fields to keep track of the offset of the click within the shape (already had)
A field to keep track of if you're dragging
Overwrite the paintComponent method to paint your shape
A MouseListener and MouseMotionListener added to the Panel (MouseAdapter does both of these)
Here's a basic working example.
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class DrawTest extends JPanel{
//Shape doesn't have a location field - you'd have to keep track of
//this yourself if you're set on using the shape interface
private Rectangle shape = new Rectangle(100, 100);
// The location within the shape you clicked
private int xOffset = 0;
private int yOffset = 0;
// To indicate dragging is happening
boolean isDragging = false;
public DrawTest(){
MouseAdapter listener = new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
// Starts dragging and calculates offset
if(shape.contains(e.getPoint())){
isDragging = true;
xOffset = e.getPoint().x - shape.x;
yOffset = e.getPoint().y - shape.y;
}
}
#Override
public void mouseReleased(MouseEvent e) {
// Ends dragging
isDragging = false;
}
#Override
public void mouseDragged(MouseEvent e) {
// Moves the shape - doesn't actually need to be a method
// but is because you had it as one
if(isDragging){
moveShape(e);
}
}
private void moveShape(MouseEvent e) {
Point newLocation = e.getPoint();
newLocation.x -= xOffset;
newLocation.y -= yOffset;
shape.setLocation(newLocation);
repaint();
}
};
//Add a mouse mostion listener (for dragging) and regular listener (for clicking)
addMouseListener(listener);
addMouseMotionListener(listener);
}
// So we have a play area to work with
public Dimension getPreferredSize(){
return new Dimension(400,300);
}
//Paints the shape
public void paintComponent(Graphics g){
super.paintComponent(g);
g.clearRect(0,0,getWidth(), getHeight());
g.fillRect(shape.x, shape.y, shape.width, shape.height);
}
public static void main(String[] args)
{
final JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new DrawTest());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
}
Tried a small simple example
Dragging the rectangle will make it move with the cursor it also checks the bounds so the rectangle cannot be dragged off screen:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class ShapeMover {
public ShapeMover() {
JFrame frame = new JFrame();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setTitle("Shape Mover");
initComponents(frame);
frame.pack();
frame.setVisible(true);
}
public static void main(String s[]) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new ShapeMover();
}
});
}
private void initComponents(JFrame frame) {
frame.getContentPane().add(new DragPanel());
}
}
class DragPanel extends JPanel {
Rectangle rect = new Rectangle(0, 0, 100, 50);
int preX, preY;
boolean isFirstTime = true;
Rectangle area;
boolean pressOut = false;
private Dimension dim = new Dimension(400, 300);
public DragPanel() {
setBackground(Color.white);
addMouseMotionListener(new MyMouseAdapter());
addMouseListener(new MyMouseAdapter());
}
#Override
public Dimension getPreferredSize() {
return dim;
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
if (isFirstTime) {
area = new Rectangle(dim);
rect.setLocation(50, 50);
isFirstTime = false;
}
g2d.setColor(Color.black);
g2d.fill(rect);
}
boolean checkRect() {
if (area == null) {
return false;
}
if (area.contains(rect.x, rect.y, 100, 50)) {
return true;
}
int new_x = rect.x;
int new_y = rect.y;
if ((rect.x + 100) > area.getWidth()) {
new_x = (int) area.getWidth() - 99;
}
if (rect.x < 0) {
new_x = -1;
}
if ((rect.y + 50) > area.getHeight()) {
new_y = (int) area.getHeight() - 49;
}
if (rect.y < 0) {
new_y = -1;
}
rect.setLocation(new_x, new_y);
return false;
}
private class MyMouseAdapter extends MouseAdapter {
#Override
public void mousePressed(MouseEvent e) {
preX = rect.x - e.getX();
preY = rect.y - e.getY();
if (rect.contains(e.getX(), e.getY())) {
updateLocation(e);
} else {
pressOut = true;
}
}
#Override
public void mouseDragged(MouseEvent e) {
if (!pressOut) {
updateLocation(e);
} else {
}
}
#Override
public void mouseReleased(MouseEvent e) {
if (rect.contains(e.getX(), e.getY())) {
updateLocation(e);
} else {
pressOut = false;
}
}
public void updateLocation(MouseEvent e) {
rect.setLocation(preX + e.getX(), preY + e.getY());
checkRect();
repaint();
}
}
}
Reference:
http://www.java2s.com/Code/Java/Event/MoveShapewithmouse.htm

Categories