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.
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've switched from KeyListeners to KeyBindings as instructed, however they still seem to do nothing. My keybinds are set up as to allow the left and right arrow keys to call a setDx() method in paddle.java which instructs the move() method to move the paddle.
gamePanel.java:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class gamePanel extends JPanel implements ActionListener {
paddle Paddle;
boolean ingame = true;
int delay = 1000;
Timer timer;
JLabel text = new JLabel("stuff here");
InputMap im = this.getInputMap(JPanel.WHEN_IN_FOCUSED_WINDOW);
ActionMap am = this.getActionMap();
public gamePanel() {
setBackground(Color.WHITE);
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0), "RightArrow");
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0), "LeftArrow");
add(text);
text.setBounds(100, 100, 200, 300);
timer = new Timer(delay, this);
Paddle = new paddle();
timer.start();
}
#Override
public void paint(Graphics g) {
super.paint(g);
if (ingame) {
g.drawImage(Paddle.getImage(), Paddle.getX(), Paddle.getY(),
Paddle.getWidth(), Paddle.getHeight(), this);
}
}
#Override
public void actionPerformed(ActionEvent ae) {
Object obj = ae.getSource();
if (obj == timer) {
Paddle.move();
validate();
repaint();
}
}
public class ArrowAction extends AbstractAction {
private String cmd;
public ArrowAction(String cmd) {
this.cmd = cmd;
}
#Override
public void actionPerformed(ActionEvent e) {
if (cmd.equalsIgnoreCase("LeftArrow")) {
Paddle.setDx(-20);
} else if (cmd.equalsIgnoreCase("RightArrow")) {
Paddle.setDx(20);
}
}
}
/*
#Override
public void keyPressed(KeyEvent ke) {
int KeyCode = ke.getKeyCode();
if (KeyCode == KeyEvent.VK_LEFT) {
text.setText("key pressed");
Paddle.setDx(-20);
}
if (KeyCode == KeyEvent.VK_RIGHT) {
Paddle.setDx(20);
}
}
#Override
public void keyReleased(KeyEvent ke) {
int KeyCode = ke.getKeyCode();
if (KeyCode == KeyEvent.VK_LEFT) {
Paddle.setDx(0);
}
if (KeyCode == KeyEvent.VK_RIGHT) {
Paddle.setDx(0);
}
}
#Override
public void keyTyped(KeyEvent ke) {
}
*/
}
Paddle.java:
import java.awt.Image;
import java.awt.Rectangle;
import javax.swing.ImageIcon;
public class paddle {
int dx = 0;
int x, y;
int height, width;
Image image;
public paddle() {
ImageIcon ii = new ImageIcon("src/Paddle.png");
image = ii.getImage();
width = image.getWidth(null);
height = image.getHeight(null);
//dx = 20;
resetState();
}
public void setDx(int z) {
dx = z;
}
public void move() {
x += dx;
if (x <= 2) {
x = 2;
}
if (x >= (640 - getWidth())) {
x = (640 - getWidth());
}
}
public void resetState() {
x = 250;
y = 375;
}
public void setX(int x) {
this.x = x;
}
public int getX() {
return x;
}
public void setY(int y) {
this.y = y;
}
public int getY() {
return y;
}
public int getWidth() {
return width;
}
public int getHeight() {
return height;
}
Image getImage() {
return image;
}
Rectangle getRect() {
return new Rectangle(x, y, image.getWidth(null), image.getHeight(null));
}
}
If your KeyListener methods are not being called I suspect it's because the correct component does not have focus. It's difficult sometimes to manage what component has focus especially in a game, so I would suggest switching over to using key bindings which doesn't require components to have focus.
You never add the corresponding actions you the ActionMap
am.put("LeftArrow", new ArrowAction("LeftArrow"));
am.put("RightArrow", new ArrowAction("RightArrow"));
Also, if you don't repaint() in the actioPerformed of the ArrowAction, you won't see it update immediately, until repaint() is called by the Timer, which isn't very long, but still a miniscule delay.
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;
}
}
}
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();
}
});
}
}
I am supposed to make a little game simulation. in this game there are three button . when user click start tank and car will close each other in 90 degrees when user click shut button tank will throw bullet to car.
i made and a simulation for this. tank throw bullet to car but when bullet crash car i couldn't this. i just need increase score of how many times tank hit car.
here is the source code
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JPanel;
public class Vehicle extends Thread {
private JPanel box;
private int XSIZE;
private int YSIZE;
private int time;
private int x;
private int y;
private int dx = 5;
private int dy = 5;
private int dim;
public Vehicle(JPanel b, int i) {
box = b;
this.dim = i;
if (i == 0) {
x = 0;
y = 100;
time = 1000;
XSIZE = 9;
YSIZE = 20;
} else {
time = 200;
y = box.getSize().height;
x = box.getSize().width / 2;
XSIZE = 6;
YSIZE = 10;
}
}
public void draw() {
Graphics g = box.getGraphics();
g.fillOval(x, y, XSIZE, YSIZE);
g.dispose();
}
public void moveHorizontal() {
if (!box.isVisible())
return;
Graphics g = box.getGraphics();
g.setColor(Color.BLUE);
g.setXORMode(box.getBackground());
g.fillOval(x, y, XSIZE, YSIZE);
x += dx;
Dimension d = box.getSize();
if (x < 0) {
x = 0;
dx = -dx;
}
if (x + XSIZE >= d.width) {
x = d.width - XSIZE;
dx = -dx;
}
g.fillOval(x, y, XSIZE, YSIZE);
g.dispose();
}
public JPanel getBox() {
return box;
}
public void setBox(JPanel box) {
this.box = box;
}
public void moveVertical() {
if (!box.isVisible())
return;
Graphics g = box.getGraphics();
g.setXORMode(box.getBackground());
g.fillOval(x, y, XSIZE, YSIZE);
y += dy;
Dimension d = box.getSize();
if (y < 0) {
y = 0;
dy = -dy;
}
if (y + YSIZE >= d.height) {
y = d.height - YSIZE;
dy = -dy;
}
g.fillOval(x, y, XSIZE, YSIZE);
g.dispose();
}
public void move(int i) {
if (i == 0) {
moveHorizontal();
} else {
moveVertical();
}
}
public int getYSIZE() {
return YSIZE;
}
public void setYSIZE(int ySIZE) {
YSIZE = ySIZE;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
public int getY() {
return y;
}
public int getXSIZE() {
return XSIZE;
}
public void setXSIZE(int xSIZE) {
XSIZE = xSIZE;
}
public void setY(int y) {
this.y = y;
}
public void run() {
try {
draw();
for (;;) {
move(dim);
sleep(time);
}
} catch (InterruptedException e) {
}
}
}
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JPanel;
public class Bullet extends Thread {
private JPanel box;
private int XSIZE = 3;
public int getXSIZE() {
return XSIZE;
}
public void setXSIZE(int xSIZE) {
XSIZE = xSIZE;
}
public int getX() {
return x;
}
public void setX(int x) {
this.x = x;
}
private int YSIZE = 1;
private int x;
private int y;
private int dx = 3;
public Bullet(JPanel b, Vehicle tank, Vehicle car) {
box = b;
x = tank.getX() + tank.getXSIZE();
if (x >= tank.getBox().getSize().width / 2)
dx = -dx;
y = tank.getY() + tank.getYSIZE() / 2;
;
}
public void draw() {
Graphics g = box.getGraphics();
g.fillOval(x, y, XSIZE, YSIZE);
g.dispose();
}
public void move() {
if (!box.isVisible())
return;
Graphics g = box.getGraphics();
g.setColor(Color.RED);
g.setXORMode(box.getBackground());
g.fillOval(x, y, XSIZE, YSIZE);
x += dx;
Dimension d = box.getSize();
if (x < 0) {
x = 0;
}
if (x + XSIZE >= d.width) {
x = d.width - XSIZE;
}
g.fillOval(x, y, XSIZE, YSIZE);
g.dispose();
}
public void run() {
try {
draw();
for (;;) {
move();
sleep(20);
}
} catch (InterruptedException e) {
}
}
}
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JLabel;
#SuppressWarnings("serial")
public class Tank_Shut_Car_ThreadFrame extends JFrame {
private JPanel canvas;
private boolean isOn = false;
private Vehicle tank;
private Vehicle car;
private JLabel score;
public static int sc = 0;
public Tank_Shut_Car_ThreadFrame() {
setResizable(false);
setSize(600, 400);
setTitle("Tank Shut Car");
addWindowListener(new WindowAdapter() {
public void windowClosing(WindowEvent e) {
System.exit(0);
}
});
Container contentPane = getContentPane();
canvas = new JPanel();
contentPane.add(canvas, "Center");
canvas.setLayout(null);
score = new JLabel("0");
score.setBounds(527, 11, 36, 14);
canvas.add(score);
JLabel lblScore = new JLabel("score");
lblScore.setBounds(481, 11, 36, 14);
canvas.add(lblScore);
JPanel p = new JPanel();
addButton(p, "Start", new ActionListener() {
public void actionPerformed(ActionEvent evt) {
if (!isOn) {
tank = new Vehicle(canvas, 0);
tank.start();
car = new Vehicle(canvas, 1);
car.start();
isOn = true;
}
}
});
addButton(p, "Shut", new ActionListener() {
public void actionPerformed(ActionEvent evt) {
if (isOn) {
Bullet bullet = new Bullet(canvas, tank, car);
bullet.start();
score.setText("" + sc);
}
}
});
addButton(p, "Close", new ActionListener() {
public void actionPerformed(ActionEvent evt) {
canvas.setVisible(false);
System.exit(0);
}
});
contentPane.add(p, "South");
}
public void addButton(Container c, String title, ActionListener a) {
JButton button = new JButton(title);
c.add(button);
button.addActionListener(a);
}
}
import javax.swing.JFrame;
public class Test {
public static void main(String[] args) {
JFrame frame = new Tank_Shut_Car_ThreadFrame();
frame.setVisible(true);
}
}
Okay, so I had a play around with this (just for fun)
Now, this is far from a "proper" or "complete" game engine, but it provides a basic idea of how it might be possible to mix a core thread engine with UI components.
Rather then pasting the entire code, I've uploaded the source it to Tank.zip
But the basic engine looks like this...
public class GameEngine extends Thread {
public static final Object ASSET_LOCK = new Object();
private List<GameAsset> lstAssets;
private GameScreen screen;
public GameEngine(GameScreen screen) {
// Let the thread die when the JVM closes
setDaemon(true);
// Want to be below the UI thread (personal preference)
setPriority(NORM_PRIORITY - 1);
// A list of game assests
lstAssets = new ArrayList<GameAsset>(25);
// A reference to the screen
this.screen = screen;
// Add global key listener, this is simpler to the trying to attach a key listener
// to the screen.
Toolkit.getDefaultToolkit().addAWTEventListener(new EventHandler(), AWTEvent.KEY_EVENT_MASK);
}
public GameAsset[] getAssets() {
synchronized (ASSET_LOCK) {
return lstAssets.toArray(new GameAsset[lstAssets.size()]);
}
}
/*
* Allows for assets to be added
*/
public void addAsset(GameAsset asset) {
synchronized (ASSET_LOCK) {
lstAssets.add(asset);
}
}
#Override
public void run() {
while (true) {
try {
sleep(40);
} catch (InterruptedException ex) {
}
synchronized (ASSET_LOCK) {
GameAsset[] assets = lstAssets.toArray(new GameAsset[lstAssets.size()]);
for (GameAsset asset : assets) {
if (lstAssets.contains(asset)) {
asset.update(this, screen);
}
}
screen.repaint(new ArrayList<GameAsset>(lstAssets));
}
}
}
/**
* Allows the removal of an asset...
*/
public void removeAsset(GameAsset asset) {
synchronized (ASSET_LOCK) {
lstAssets.remove(asset);
}
}
/**
* Key event handling...
*/
protected class EventHandler implements AWTEventListener {
#Override
public void eventDispatched(AWTEvent event) {
KeyEvent keyEvent = (KeyEvent) event;
if (keyEvent.getID() == KeyEvent.KEY_PRESSED) {
synchronized (ASSET_LOCK) {
GameAsset[] assets = lstAssets.toArray(new GameAsset[lstAssets.size()]);
for (GameAsset asset : assets) {
if (lstAssets.contains(asset)) {
asset.processKeyPressed(GameEngine.this, screen, keyEvent);
}
}
}
} else if (keyEvent.getID() == KeyEvent.KEY_RELEASED) {
synchronized (ASSET_LOCK) {
GameAsset[] assets = lstAssets.toArray(new GameAsset[lstAssets.size()]);
for (GameAsset asset : lstAssets) {
if (lstAssets.contains(asset)) {
asset.processKeyReleased(GameEngine.this, screen, keyEvent);
}
}
}
}
}
}
}
Swing is not thread safe. Events should be fired on the event-dispatching thread.
http://www.javamex.com/tutorials/threads/invokelater.shtml