I am trying to get an animation using a spritesheet in JFrame. The problem is the once loaded, the image doesn't change. It only shows the first image.
I have searched a lot and tried almost all the advice but can't get it to work.
By the way, getSprite has a return type of Image.
When I do everything in JFrame, only one image is shown. When I do it in separate class extending JPanel, I get only a white Window.
Update 1: Thanks to andrew, got JFrame working. Fixed my old part too. Here's the working code at ideone
Update 2:
Here's my version with timer.
class TestSpriteSheet extends JFrame{
//same old variables
public TestSpriteSheet(){
//same old stuff goes here before this
add(new PanelSprite(this, ss));
this.setVisible(true);
}
}
class PanelSprite extends JPanel{
private long runningTime = 0;
private int fps = 3;
private boolean stop = false;
private SpriteSheetManager ss;
private TestSpriteSheet temp;
public PanelSprite(TestSpriteSheet test, SpriteSheetManager sm){
ss = sm;
temp = test;
setSize(180,180);
setLayout(new BorderLayout()); init();
}
public void paintComponent(Graphics g){
super.paintComponent(g);
long time = 5000;
animate_with_gfx(g, time);
}
public void init(){
Timer t = new Timer((int)(1000/fps), new ActionListener() {
public void actionPerformed(ActionEvent e) {
if(!stop) {
repaint();
} else {
((Timer)e.getSource()).stop();
}
}
});
t.setRepeats(true);
t.setDelay((int)(1000/fps));
t.start();
}
public void animate_with_gfx(Graphics g, long time){
if(runningTime <= time){
try {
System.out.println(runningTime); //Checking if this part works
int x = 0; int y = 0;
g.drawImage(ss.getSprite(x, y), 40, 40, null);
x++; y++; runningTime+=(1000/fps);
}catch (Exception ex) {
ex.printStackTrace();
}
}
else{
stop = true;
}
}
}
public void paintComponent(Graphics g){
super.paintComponent(g);
long time = 5000; int fps = 3;
boolean stop = false;
Timer t = new Timer((int)(1000/fps), new ActionListener() {
public void actionPerformed(ActionEvent e) {
if(!stop) {
animate_with_gfx(g, time, fps, stop);
repaint();
} else {
((Timer)e.getSource()).stop();
}
}
});
t.setRepeats(true);
t.setDelay((int)(1000/fps));
t.start();
System.exit(0);
}
This part is entirely wrong, since the paintComponent() method is called whenever the JRE thinks the view needs repainting. So definitely remove the System.exit(0);!
Then the Timer needs a single instance, to be started once. That would best be done in the constructor or an init() type method.
It might look something like this:
private int fps = 3;
private boolean stop = false;
public void paintComponent(Graphics g){
super.paintComponent(g);
long time = 5000;
animate_with_gfx(g, time, fps, stop);
}
/** Called from constructor.. */
public void init(){
Timer t = new Timer((int)(1000/fps), new ActionListener() {
public void actionPerformed(ActionEvent e) {
if(!stop) {
repaint();
} else {
((Timer)e.getSource()).stop();
}
}
});
t.setRepeats(true);
t.setDelay((int)(1000/fps));
t.start();
}
Update
import java.awt.*;
import java.awt.image.BufferedImage;
import java.awt.event.*;
import javax.swing.*;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
class SpriteSheetManager {
private BufferedImage spriteSheet;
int cols;
int rows;
public SpriteSheetManager() {
setSpriteSheet();
}
public void setSpriteSheet() {
try {
spriteSheet = ImageIO.read(
new URL("http://s8.postimg.org/vso6oed91/spritesheet.png"));
setColsAndRows(3, 3);
} catch (IOException e) {
e.printStackTrace();
}
}
public BufferedImage getSpriteSheet() {
return spriteSheet;
}
public void setColsAndRows(int cols, int rows) {
this.cols = cols;
this.rows = rows;
}
public Image getSprite(int x, int y) {
Image sprite = null;
try {
sprite = spriteSheet.getSubimage(
x * spriteSheet.getWidth() / cols,
y * spriteSheet.getHeight() / rows,
spriteSheet.getWidth() / cols,
spriteSheet.getHeight() / rows);
} catch (Exception e) {
e.printStackTrace();
}
return sprite;
}
}
class Ideone {
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TestSpriteSheet();
}
});
}
}
class TestSpriteSheet extends JFrame {
private static final long serialVersionUID = 1L;
private SpriteSheetManager ss;
public TestSpriteSheet() {
super("Testing SpriteSheets");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setResizable(false);
setLayout(new BorderLayout());
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent windowEvent) {
System.exit(0);
}
});
ss = new SpriteSheetManager();
add(new PanelSprite(this, ss));
pack();
setSize(200, 200);
this.setVisible(true);
}
}
class PanelSprite extends JPanel {
private long runningTime = 0;
private int fps = 10;
private boolean stop = false;
private SpriteSheetManager ss;
private TestSpriteSheet temp;
private Timer t;
int count = 0;
long time = 50000;
public PanelSprite(TestSpriteSheet test, SpriteSheetManager sm) {
ss = sm;
temp = test;
init();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
animate_with_gfx(g);
}
public void init() {
t = new Timer((int) (1000 / fps), new ActionListener() {
public void actionPerformed(ActionEvent e) {
if (!stop) {
repaint();
} else {
((Timer) e.getSource()).stop();
}
}
});
t.setRepeats(true);
t.setDelay((int) (1000 / fps));
t.start();
}
public void animate_with_gfx(Graphics g) {
if (runningTime <= time) {
Image img = ss.getSprite((count % 9) % 3, (count % 9) / 3);
g.drawImage(img, 40, 40, this);
count++;
runningTime += (1000 / fps);
} else {
stop = true;
}
}
}
Related
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.
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().
Good Morning, I was building when i find the problem that I don't know how to make my Player jump.
There are my classes:
GamePanel
package src;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
public class GamePanel extends JPanel implements Runnable{
public static final int WIDTH=300, HEIGHT=300, MIN_X_COORD=0, MIN_Y_COORD=0, MAX_X_COORD=19, MAX_Y_COORD=19;
private Thread thread;
private boolean playing, key=false;
private Random r;
int xCoord = 0, yCoord = 16, size = 15;
char direction;
private Player p;
private Victory v;
private ArrayList<Ground> grounds;
private ArrayList<Grass> grasses;
private ArrayList<Sky> sky;
Action jump=new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
yCoord--;
yCoord--;
yCoord++;
yCoord++;
}
};
Action right=new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
if(xCoord!=MAX_X_COORD)xCoord++;
direction='l';
}
};
Action left=new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
if(xCoord!=MIN_X_COORD)xCoord--;
direction='r';
}
};
public GamePanel() {
this.getInputMap().put(KeyStroke.getKeyStroke('d'),"right");
this.getInputMap().put(KeyStroke.getKeyStroke('a'),"left");
this.getInputMap().put(KeyStroke.getKeyStroke("SPACE"),"up");
this.getActionMap().put("right", right);
this.getActionMap().put("left", left);
this.getActionMap().put("up", jump);
setFocusable(true);
setPreferredSize(new Dimension(WIDTH, HEIGHT));
grounds = new ArrayList<Ground>();
grasses=new ArrayList<Grass>();
r=new Random();
v=new Victory(16,19, size);
p=new Player(xCoord, yCoord, size);
sky=new ArrayList<Sky>();
start();
}
public void start() {
playing=true;
thread=new Thread(this);
thread.start();
}
#Override
public void run() {
while (playing) {
tick();
repaint();
}
}
public void stop() {
playing = false;
// now we create the Game Over Screen
JFrame f=new JFrame("Game Over Screen");
JButton b=new JButton("Press this to play again!");
f.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE);
f.setPreferredSize(new Dimension(500,500));
JButton close = new JButton("Quit!");
b.setBounds(130,200,250, 40);
f.add(b);
f.add(close);
f.setLayout(new FlowLayout());
f.pack();
f.setVisible(true);
f.setLocationRelativeTo(null);
Main.returnFrame().dispatchEvent(new WindowEvent(Main.returnFrame(), WindowEvent.WINDOW_CLOSING));
Main.returnFrame().dispose();
b.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
f.dispatchEvent(new WindowEvent(f, WindowEvent.WINDOW_CLOSING));
Main.main(null);
}
});
close.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
System.exit(0);
}
});
try {
thread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void tick() {
p=new Player(xCoord, yCoord, size);
if(grounds.size()==0) {
for(int y=MAX_Y_COORD;y>17;y--)
for(int x = 0;x<=MAX_X_COORD;x++)
grounds.add(new Ground(x,y,size));
}
if(grasses.size()==0)
for (int x=0;x<=MAX_X_COORD;x++)
grasses.add(new Grass(x, 17, size));
if (v.getxCoord()==p.getxCoord()&&v.getyCoord()==p.getyCoord()) {
win();
}
if (sky.size()==0)
for (int x=MIN_X_COORD;x<=MAX_X_COORD;x++)
for (int y=MIN_Y_COORD;y<17;y++)
sky.add(new Sky(x, y, size));
for(int i = 0;i< grounds.size();i++)
if(p.getxCoord() == grounds.get(i).getX_COORD() && p.getyCoord() ==grounds.get(i).getY_COORD())
back();
for(int i = 0;i< grasses.size();i++)
if(p.getxCoord() == grasses.get(i).getX_COORD() && p.getyCoord() ==grasses.get(i).getY_COORD())
back();
}
#Override
public void paint(Graphics g) {
g.clearRect(0, 0, WIDTH, HEIGHT);
g.setColor(Color.black);
g.fillRect(0, 0, WIDTH, HEIGHT);
for(int i = 0;i<WIDTH/10;i++) { // draws lines
g.drawLine(i*10, 0, i*10, HEIGHT);
}
for(int i = 0;i<HEIGHT/10;i++) {
g.drawLine(0, i*10, HEIGHT, i*10);
}
for(int i = 0;i<grounds.size();i++) {
grounds.get(i).draw(g);
}
for (Grass grass : grasses) {
grass.draw(g);
}
for (Sky s:sky)
s.draw(g);
v.draw(g);
p.draw(g);
}
public void win() {
JOptionPane.showMessageDialog(null, "Hai Vinto");
stop();
}
public void die() {
JOptionPane.showMessageDialog(null, "Sei morto");
stop();
}
public void back() {
switch (direction) {
case 'l': xCoord++;break;
case 'r': xCoord--;break;
}
}
}
Player
import java.awt.Color;
import java.awt.Graphics;
public class Player {
int xCoord, yCoord, width, height;
public Player(int xCoord, int yCoord, int size) {
this.xCoord=xCoord;
this.yCoord=yCoord;
width=size;
height=size;
}
public void draw(Graphics g) {
g.setColor(Color.RED);
g.fillRect(xCoord*width, yCoord*height, width, height);
}
public void setxCoord(int xCoord) {
this.xCoord = xCoord;
}
public int getxCoord() {
return xCoord;
}
public int getyCoord() {
return yCoord;
}
public void setyCoord(int yCoord) {
this.yCoord = yCoord;
}
}
I need that the action jump make the player character jump of two squares and return down.
I thought that i had to increase the yCoord and to repaint but it doesn't affect
When i try to set value to BufferedImage called dinoImage in Dino.java in a constructor i just get a blank screen every time (second picture) because repaint() is not being called, but if i set it to null it is working just fine but without this image (first picture).
No exceptions, everything seems fine in this code, this problem appears when i try to set value to this field using static method getImage of Resource.java which uses this line of code ImageIO.read(new File(path)) and it causes that repaint() is not being called, i guess this line causes such weird behavior but i dont know how to solve it.
Main.java
public class Main {
public static void main(String[] args) {
GameWindow gameWindow = new GameWindow();
gameWindow.startGame();
}
}
GameWindow.java
public class GameWindow extends JFrame {
private GameScreen gameScreen;
public GameWindow() {
super("Runner");
setSize(1000, 500);
setVisible(true);
setResizable(false);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
gameScreen = new GameScreen();
add(gameScreen);
}
public void startGame() {
gameScreen.startThread();
}
}
GameScreen.java
public class GameScreen extends JPanel implements Runnable, KeyListener {
private Thread thread;
public static final double GRAVITY = 0.1;
public static final int GROUND_Y = 300;
private Dino dino;
public GameScreen() {
thread = new Thread(this);
dino = new Dino();
}
public void startThread() {
thread.start();
}
#Override
public void run() {
while(true) {
try {
Thread.sleep(20);
dino.updatePosition();
repaint();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
// g.clearRect(0, 0, getWidth(), getHeight());
g.setColor(Color.RED);
g.drawLine(0, GROUND_Y, getWidth(), GROUND_Y);
dino.draw(g);
}
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e) {
System.out.println("Key Pressed");
dino.jump();
}
#Override
public void keyReleased(KeyEvent e) {
System.out.println("Key Released");
}
}
Dino.java
public class Dino {
private double x = 100;
private double y = 100;
private double speedY = 0;
private BufferedImage dinoImage;
public Dino() {
dinoImage = getImage("data/dino.png");
}
public void updatePosition() {
if(y + speedY >= GROUND_Y - 100) {
speedY = 0;
y = GROUND_Y - 100;
} else {
speedY += GRAVITY;
y += speedY;
}
}
public void jump() {
if(y == GROUND_Y - 100) {
speedY = -5;
y += speedY;
}
}
public void draw(Graphics g) {
g.setColor(Color.BLACK);
g.drawRect((int)x, (int)y, 100, 100);
g.drawImage(dinoImage, (int)x, (int)y, null);
}
}
Resource.java
public class Resource {
public static BufferedImage getImage(String path) {
BufferedImage image = null;
try {
image = ImageIO.read(new File(path));
} catch (IOException e) {
e.printStackTrace();
}
return image;
}
}
setSize(1000, 500);
setVisible(true);
setResizable(false);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
gameScreen = new GameScreen();
add(gameScreen);
Swing components need to be added to the frame BEFORE the frame is made visible. Otherwise the panel has a size of (0, 0) and there is nothing to paint.
The code should be something like:
gameScreen = new GameScreen();
add(gameScreen);
setSize(1000, 500);
setVisible(true);
I want to, when i click the "d" button, start a timer. This is to animate a player walking. The timer doesn't start when i press the key, how do i do that?
The code I have is this:
public class Game extends JPanel implements KeyListener {
//Player variables
private BufferedImage playerStanding;
private BufferedImage playerWalking;
private BufferedImage playerFrame;
private boolean walking = false;
private final int PLAYER_HEIGHT = 100;
private final int PLAYER_WIDTH = 100;
private final int INITIAL_X = 0;
private final int INITIAL_Y = 500;
private int x = INITIAL_X;
private int y = INITIAL_Y;
//The timer I want to start on keypress-> "d"
private Timer playerAnimationTimer;
public Game() {
setPreferredSize(new Dimension(800, 800));
setBackground(Color.CYAN);
//Player
try {
playerStanding = ImageIO.read(getClass().getResource("player1.gif"));
playerWalking = ImageIO.read(getClass().getResource("player2.gif"));
playerFrame = playerStanding;
}
catch (IOException ex) {
ex.printStackTrace();
}
playerAnimationTimer = new Timer(500, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
walking = !walking;
playerFrame = walking ? playerWalking : playerStanding;
x += 10;
if (x > getWidth() - PLAYER_WIDTH) {
x = INITIAL_X;
}
repaint();
}
});
playerAnimationTimer.setRepeats(true);
}
public Dimension setPreferredSize() {
return new Dimension(800, 800);
}
#Override
public void paintComponent(Graphics graphics) {
super.paintComponent(graphics);
Graphics2D graphics2D = (Graphics2D) graphics;
if (playerFrame != null) {
graphics2D.drawImage(playerFrame, x, y, PLAYER_WIDTH, PLAYER_HEIGHT, this);
}
graphics2D.dispose();
}
#Override
public void keyTyped(KeyEvent e) {
//This doesn't work
playerAnimationTimer.start();
}
#Override
public void keyPressed(KeyEvent e) {
}
#Override
public void keyReleased(KeyEvent e) {
}
}
//The class to hold the gamepanel
public class StartGame extends JFrame implements ActionListener {
private static JButton startGame = new JButton();
StartGame() {
setDefaultCloseOperation(WindowConstants.HIDE_ON_CLOSE);
setSize(200, 100);
setVisible(true);
setBackground(Color.BLUE);
setLocationRelativeTo(null);
startGame.setText("Play!");
startGame.setSize(100, 25);
startGame.addActionListener(this);
add(startGame);
}
#Override
public void actionPerformed(ActionEvent e) {
if (e.getSource() == startGame) {
this.setVisible(false);
new GameWindow();
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new StartGame();
}
});
}
}
How could I make the timer start when I click the "d" button?
Your KeyListener doesn't work because you never add the KeyListener to anything much less to a component that has focus, which is needed for a KeyListener to work.
I suggest that you instead use Key Bindings as a cleaner safer way to capture the desired key press.
As an aside, never dispose of a Graphics object that is given to you from the JVM.
For a better answer, please edit your code to make it comply with the mcve standard. You should use no images files, and it should compile and run for us unaltered.
You could set the private Timer like you did and start it like this...
public void startTimer(){
timer.start();
timer.setRepeats(true);
}