I'm going to create a moving circle for my later project, and the circle will keep moving, and it interior color will change like color emitting , the changing color will from little circle to larger circle in 5 levels, so how to keep each color change to stay a while and I hope these code present with thread, so I create two thread for the purpose, one control circle moving, another control the circle's interior color emit
here is my code:
import java.awt.*;
import static java.awt.Color.black;
import static java.awt.Color.yellow;
import static java.awt.FlowLayout.RIGHT;
import java.awt.event.*;
import java.awt.geom.Arc2D;
import java.awt.geom.Ellipse2D;
import java.awt.geom.GeneralPath;
import java.awt.geom.Rectangle2D;
import static java.lang.Math.abs;
import java.util.Random;
import javax.swing.*;
import java.util.concurrent.ExecutorService;
class thepane extends JPanel implements Runnable{
public float x,y,r;
public float speedx,speedy;
thepane(float lx,float ly,float lr, float sx,float sy){
loadspeed(sx,sy);
load(lx,ly,lr);
for(int i=0;i<5;i++)
fc[i]=new Color(nd.nextInt(255),nd.nextInt(255),nd.nextInt(255));
}
public void load(float lx,float ly,float lr){
x=lx;y=ly;r=lr;
}
public void loadspeed(float sx,float sy){
speedx=sx;speedy=sy;
}
public void xmoving(){
x+=speedx;
}
public void ymoving(){
y-=speedy;
}
public void touchbond(){
if(x>getWidth()-r||x<0)
speedx*=-1;
if(y>getHeight()-r||y<0)
speedy*=-1;
if(x>getWidth()-r)
x=getWidth()-r;
else if(x<0)
x=0;
if(y>getHeight()-r)
y=getHeight()-r;
else if(y<0)
y=0;
}
Random nd=new Random();
int colorcount=0;
int emitcount=0;
boolean emit=false;
Color[] fc=new Color[5];
Graphics2D comp2D ;
Thread athread;
#Override
public void paintComponent(Graphics comp) {
comp2D = (Graphics2D) comp;
//create rectangle background
comp2D.setColor(Color.BLACK);
comp2D.fillRect(0, 0, getWidth(), getHeight());
//set information text
comp2D.setFont( new Font("Arial", Font.BOLD, 12));
comp2D.setColor(Color.WHITE);
comp2D.drawString("Centre("+(x+r/2)+' '+(y+r/2)+"), xspeed: "+speedx+" yspeed: "+speedy, 10f,10f );
comp2D.drawString("panel width "+getWidth()+" panel height "+getHeight()+" circle radius "
+r, 10f, 22f);
}
//thread run()
#Override
public void run() {
x=100;y=100;
System.out.println("thread in pane start!!!! (current colorcount = "+colorcount+')');
while(true){
circleEmit(fc[colorcount%5]);
repaint();
sleeping(1);
// comp2D=(Graphics2D)this.getGraphics();
// colorEmit(comp2D);
}
}
//wait method
public void waiting(){
try{wait();}
catch(Exception e){}}
public void waiting2D(int time){
try{comp2D.wait(time);}
catch(Exception e){}
}
public void waiting(int time){
try{wait(time);}
catch(Exception e){}
}
//sleep method
public void sleeping(int n){
try{
Thread.sleep(n);
}catch(Exception f){
System.out.print(f);
}
}
Ellipse2D.Float[] e=new Ellipse2D.Float[5];
public void loade(){
float centrex=x+r/2,centrey=y+r/2;
e[0]= new Ellipse2D.Float(centrex-r/10, centrey-r/10, r/5, r/5);
e[1]= new Ellipse2D.Float(centrex-r/5, centrey-r/5, 2*r/5, 2*r/5);
e[2]= new Ellipse2D.Float(centrex-3*r/10, centrey-3*r/10, 3*r/5, 3*r/5);
e[3]= new Ellipse2D.Float(centrex-2*r/5, centrey-2*r/5, 4*r/5, 4*r/5);
e[4]= new Ellipse2D.Float(centrex-r/2, centrey-r/2, r, r);
}
public Color ff;
public synchronized void circleEmit(Color fc){
comp2D=(Graphics2D)this.getGraphics();
loade();
comp2D.setColor(fc);
comp2D.fill(e[emitcount%5]);
waiting(5);
emitcount++;
}
public synchronized void callnotify(){
this.notify();
}
//iterative way to generate color emit
public void colorEmit(Graphics2D comp2D){
//create circle
//set circle property
float centrex=x+r/2,centrey=y+r/2;//so x=centrex-r/2;y=centrey+r/2
Ellipse2D.Float e1 = new Ellipse2D.Float(centrex-r/10, centrey-r/10, r/5, r/5);
Ellipse2D.Float e2 = new Ellipse2D.Float(centrex-r/5, centrey-r/5, 2*r/5, 2*r/5);
Ellipse2D.Float e3 = new Ellipse2D.Float(centrex-3*r/10, centrey-3*r/10, 3*r/5, 3*r/5);
Ellipse2D.Float e4 = new Ellipse2D.Float(centrex-2*r/5, centrey-2*r/5, 4*r/5, 4*r/5);
Ellipse2D.Float e5 = new Ellipse2D.Float(centrex-r/2, centrey-r/2, r, r);
if(colorcount>=4)
emit(comp2D,fc[(colorcount-4)%5],e5);
waiting(1000);
if(colorcount>=3)
emit(comp2D,fc[(colorcount-3)%5],e4);
waiting(1000);
if(colorcount>=2)
emit(comp2D,fc[(colorcount-2)%5],e3);
waiting(1000);
if(colorcount>=1)
emit(comp2D,fc[(colorcount-1)%5],e2);
waiting(1000);
emit(comp2D,fc[colorcount%5],e1);
waiting(1000);
colorcount++;
}
private void emit(Graphics2D comp,Color thecolor,Ellipse2D.Float f){
comp.setColor(thecolor);
comp.fill(f);
}
}
//------------------------------------------------------------------------------------
//main class
public class drawpanel extends Thread implements ActionListener{
JFrame frame=new JFrame();
thepane panel;
JButton FlyingBalls=new JButton("balls"),exit=new JButton("Exit"),stop=new JButton("Stop");
JButton slow=new JButton("slow down"),resume=new JButton("resume");
Float x,y,r;
public void sleeping(int n){
try{
Thread.sleep(n);
}catch(Exception f){
System.out.print(f);
}
}
Thread newthread,pthread;
Thread[] five=new Thread[5];
drawpanel(){
frame.setTitle("FlyingBalls");
frame.setLocation(100, 100);
frame.setLayout(null);
//x,y,r,speedx,speedy
panel=new thepane(nd.nextInt(800),nd.nextInt(500),40,nd.nextFloat()*20+1,nd.nextFloat()*10+1);
panel.setSize(800,500);
frame.setSize(810,580);
frame.add(panel);
FlyingBalls.setSize(80,30);exit.setSize(70,30);stop.setSize(70,30);slow.setSize(140,30);
resume.setSize(100,30);
FlyingBalls.addActionListener(this);
exit.addActionListener(this);
stop.addActionListener(this);slow.addActionListener(this);resume.addActionListener(this);
frame.add(FlyingBalls);frame.add(exit); frame.add(stop);frame.add(slow);frame.add(resume);
FlyingBalls.setLocation(20,500);exit.setLocation(190, 500);stop.setLocation(110,500);
slow.setLocation(270,500);resume.setLocation(420,500);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
//control moving ball
newthread=new Thread(this);
//control color change
for(int i=0;i<5;i++){
five[i]=new Thread(panel);
}
// newthread.start();
panel.colorcount++;
five[0].start();
panel.colorcount=2;
// five[1].start();
panel.waiting(5);
}
public static void main(String[] arg){
drawpanel apanel=new drawpanel();
}
int bw=800,bh=500;
void setp(){
x=panel.x;y=panel.y;
}
void touchbond(){
System.out.println("width:"+panel.getWidth()+"Height:"+panel.getHeight());
System.out.println("xposition:"+x+"yposition:"+y);
if(x+r>panel.getWidth()){
panel.speedx*=-1;
x=bw-r;
}
else if(x-r<0){
panel.speedx*=-1;
x=r;
}
if(y-r<0){
panel.speedy*=-1;
y=r;
}
else if(y+r>panel.getHeight()){
panel.speedy*=-1;
y=bh-r;
}
panel.x=x;panel.y=y;
}
int T=10;
Random nd=new Random();
#Override
public void run(){
r=panel.r;
panel.loadspeed(-6.33f,-3.4f);
while(true){
if(stopcount==0){//button control variable
panel.xmoving();panel.ymoving();
panel.touchbond();
sleeping(T);}
panel.loade();
// panel.callnotify();
// panel.colorEmit(panel.comp2D);
panel.repaint();
}
}
#Override
public void start(){
}
int count=0,stopcount=0;
#Override
public void actionPerformed(ActionEvent e) {
if(e.getSource()==exit){
System.exit(0);
}
if(e.getSource()==FlyingBalls){
//panel=new thepane();
}
if(e.getSource()==resume){
stopcount=0;T=10;
panel.emit=false;
}
if(e.getSource()==slow){
if(count%2==0)
T=500;
else
T=10;
count++;
}
if(e.getSource()==stop){
stopcount++;
panel.emit=true;
}
}
}
So, lots of theory to cover.
Firstly...
Animation is not easy, good animation is hard.
Swing is single threaded and is not thread safe
This means that you should not perform any long running or blocking operations within the context of the Event Dispatching Thread.
It also means that you shouldn't modify the UI or anything the UI relies on from outside the context of the Event Dispatching Thread
More threads != more work
More threads doesn't always mean you're going to get more done. In fact, in this scenario, it could really cause a huge number of issues, as you need the ability to reason out the state at a single point in time (when painting)
Animation Theory
Okay, animation is simply the illusion of change, how you accomplish that will come down to the problem you trying to solve.
For me, the best animations are time based animations, not linear.
A linear animation keeps updating from its start state till it reaches its end state, in a constant progression. These don't tend to scale well and can suffer issues on low performant systems.
A time based animation is one where the amount of time is defined and then, based on a anchor time (ie start time) and the state of the animation is updated based on the amount of time which is passed. This is a really simple way to achieve "frame dropping". You'd also be very surprised to find that in general terms, time based animations tend to look better across more platforms.
A time based animation is also more capable of generating "easement" effects, but that's getting way deeper then we need to go right now.
Okay, but what's this got to do with your problem? Well, actually, quite a bit.
The first thing we need is some kind of "main-loop" from which all the animation can be driven. Typically, I'd look to a good animation library, but failing that, a simple Swing Timer will do the basic good really well.
It generates its ticks in the Event Dispatching Thread, which makes it very useful for our needs. See How to Use Swing Timers for more details
So, we start with something like...
private Timer timer;
//...
timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
// Update the state
repaint();
}
});
//...
timer.start();
This gives us our "main loop", from which we can update the state as needed and then trigger a repaint of the component.
For the purpose of this demonstration, I'm going to devise a self-contained unit of "animation duration", used to track the amount of time which has passed since the animation was started, this is personal choice, but it would allow me to drive a number of animations and it contains the logic to a single unit of work.
public class AnimationDuration {
private Duration duration;
private Instant startedAt;
public AnimationDuration(Duration duration) {
this.duration = duration;
}
public Duration getDuration() {
return duration;
}
public void start() {
startedAt = Instant.now();
}
public void stop() {
startedAt = null;
}
public boolean isRunning() {
return startedAt != null;
}
public float getProgress() {
Duration runningTime = Duration.between(startedAt, Instant.now());
if (runningTime.compareTo(duration) > 0) {
runningTime = duration;
}
long total = duration.toMillis();
float progress = runningTime.toMillis() / (float) total;
return progress;
}
}
This basically allows to trigger the animation to start running (anchor point in time) and then get the progress of the animation at any point in time. This provides a normalised concept from 0-1, so if we want to make it longer or shorter, all we do is adjust the duration and everything else is taken care of.
For your specific problem, I'd consider some kind of "time line" or "key frames", which defines that certain actions should occur at certain points of time along the time line.
Now, the following is a really simple concept, but it gets the job.
public interface KeyFrame {
public float getProgress();
}
public class TimeLine<K extends KeyFrame> {
private List<K> keyFrames;
public TimeLine() {
keyFrames = new ArrayList<>(25);
}
// Returns the key frames between the current progression
public K getKeyFrameAt(float progress) {
for (int index = 0; index < keyFrames.size(); index++) {
K keyFrame = keyFrames.get(index);
if (progress >= keyFrame.getProgress()) {
if (index + 1 < keyFrames.size()) {
K nextFrame = keyFrames.get(index + 1);
// But only if your between each other
if (progress < nextFrame.getProgress()) {
return keyFrame;
}
} else {
// Nothing after me :D
return keyFrame;
}
}
}
return null;
}
public void add(K keyFrame) {
keyFrames.add(keyFrame);
Collections.sort(keyFrames, new Comparator<KeyFrame>() {
#Override
public int compare(KeyFrame lhs, KeyFrame rhs) {
if (lhs.getProgress() > rhs.getProgress()) {
return 1;
} else if (lhs.getProgress() < rhs.getProgress()) {
return -1;
}
return 0;
}
});
}
}
This allows you to define certain KeyFrames along the timeline, based on a normalised concept of time and then provides the ability to get the KeyFrame based on the current progression through animation.
There are much more complex solutions you might consider, which would generate self contained events based on time progressions automatically, but I prefer been able to driver the animation itself independently, makes these types of things more flexible - add a JSlider and you can manipulate the progression manually ;)
The next thing we need is something to carry the properties for the circle KeyFrame ...
public class CirclePropertiesKeyFrame implements KeyFrame {
private float progress;
private double radius;
private Color color;
public CirclePropertiesKeyFrame(float progress, double radius, Color color) {
this.progress = progress;
this.radius = radius;
this.color = color;
}
#Override
public float getProgress() {
return progress;
}
public Color getColor() {
return color;
}
public double getRadius() {
return radius;
}
#Override
public String toString() {
return "KeyFrame progress = " + getProgress() + "; raidus= " + radius + "; color = " + color;
}
}
Now, we need to put it together...
public class TestPane extends JPanel {
private AnimationDuration timelineDuration;
private TimeLine<CirclePropertiesKeyFrame> timeLine;
private Timer timer;
private CirclePropertiesKeyFrame circleProperties;
public TestPane() {
timelineDuration = new AnimationDuration(Duration.ofSeconds(10));
timeLine = new TimeLine<>();
timeLine.add(new CirclePropertiesKeyFrame(0, 5, Color.CYAN));
timeLine.add(new CirclePropertiesKeyFrame(0.2f, 10, Color.BLUE));
timeLine.add(new CirclePropertiesKeyFrame(0.4f, 15, Color.GREEN));
timeLine.add(new CirclePropertiesKeyFrame(0.6f, 20, Color.YELLOW));
timeLine.add(new CirclePropertiesKeyFrame(0.8f, 25, Color.MAGENTA));
timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
if (timelineDuration.isRunning()) {
float progress = timelineDuration.getProgress();
if (progress >= 1.0) {
timelineDuration.stop();
}
CirclePropertiesKeyFrame keyFrame = timeLine.getKeyFrameAt(progress);
circleProperties = keyFrame;
}
repaint();
}
});
}
#Override
public void addNotify() {
super.addNotify();
timelineDuration.start();
timer.start();
}
#Override
public void removeNotify() {
super.removeNotify();
timer.stop();
timelineDuration.stop();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g.create();
if (circleProperties != null) {
double radius = circleProperties.radius;
double xPos = (getWidth() / 2) - radius;
double yPos = (getHeight() / 2) - radius;
g2d.setColor(circleProperties.color);
g2d.fill(new Ellipse2D.Double(xPos, yPos, radius * 2, radius * 2));
}
g2d.dispose();
}
}
And then we end up with something like...
Now, this is a 10 second sequence, so every 2 seconds it will update. Try changing the duration of the AnimationDuration and see what happens.
Note This is a non-repeating animation (it doesn't loop). You could make it loop, but the calculation to do so becomes more complicate, as you need to consider by how much you're over the expected Duration and then apply that to the next cycle, so it looks smooth
But what about movement?
Well, actually, pretty much already answered that question. You would also place the movement code inside the Timers ActionListener, right before the repaint request. In fact, I might be tempted to create some kind of class that could take the current KeyFrame information and combine it with the location properties, this would then be used the paintComponent method to draw the circle.
I want to blend the animation states ...
Well, that's a much more difficult question, especially when it comes to colors.
The basic theory is, you need the two key frames which set either side of the current progression. You would then apply a "blending" algorithm to calculate the amount of change to be applied between the two key frames.
Not impossible, just a step more difficult
I have a simple Java/Swing application that tries to animate a box by moving it from left to right:
public class TestFrame extends JFrame {
protected long startTime = new Date().getTime();
public class MainPanel extends JPanel {
#Override
protected void paintComponent(Graphics g) {
// calculate elapsed time
long currentTime = new Date().getTime();
long timeDiff = currentTime - TestFrame.this.startTime;
// animation time dependent
g.fillRect((int) (timeDiff / 100), 10, 10, 10);
}
}
public class MainLoop implements Runnable {
#Override
public void run() {
while (true) {
// trigger repaint
TestFrame.this.repaint();
}
}
}
public static void main(String[] args) {
new TestFrame();
}
public TestFrame() {
// init window
this.setTitle("Test");
this.setSize(500, 500);
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.add(new MainPanel());
this.setVisible(true);
// start render loop
Thread loop = new Thread(new MainLoop());
loop.start();
}
}
The problem is that the animation is not clean and the box jumps (sometimes) a few pixels. I already did some researches and according to them it should work fine if using paintComponent (instead of paint) and doing a time based animation (not frame based). I did both but the animation is still not clean.
Could anybody give me a hint what is going wrong?
You should give your while-true-loop a little rest. You're kind of burning your CPU! You're generating a tremendous amount of paint events; at some time which the thread scheduler decides, the scheduler hands off to the event dispatching thread, which as far as I recall may collapse your trillon of paint events into a single one and eventually execute paintComponent.
In the following example, the thread sleeps 20ms, which gives you a maximum frame rate of 50fps. That should be enough.
while (true) {
// trigger repaint
TestFrame.this.repaint();
try {
Thread.sleep(20);
} catch(InterruptedException exc() {
}
}
I made a few changes to your code.
I called the SwingUtilities invokeLater method to create and use your Swing components on the Event Dispatch thread.
I called the System currentTimeinMillis method to get the current time.
Instead of setting the JFrame size, I set the size of the JPanel and packed the JFrame. I reduced the size of the JPanel to speed up the repainting.
I added a delay in the while(true) loop, as fjf2002 suggested in his answer.
Here's the revised and formatted code:
package com.ggl.testing;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class TestFrame extends JFrame {
private static final long serialVersionUID = 272649179566160531L;
protected long startTime = System.currentTimeMillis();
public class MainPanel extends JPanel {
private static final long serialVersionUID = 5312139184947337026L;
public MainPanel() {
this.setPreferredSize(new Dimension(500, 30));
}
#Override
protected void paintComponent(Graphics g) {
// calculate elapsed time
long currentTime = System.currentTimeMillis();
long timeDiff = currentTime - TestFrame.this.startTime;
// animation time dependent
g.fillRect((int) (timeDiff / 100), 10, 10, 10);
}
}
public class MainLoop implements Runnable {
#Override
public void run() {
while (true) {
// trigger repaint
TestFrame.this.repaint();
try {
Thread.sleep(20L);
} catch (InterruptedException e) {
}
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new TestFrame();
}
});
}
public TestFrame() {
// init window
this.setTitle("Test");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.add(new MainPanel());
this.pack();
this.setVisible(true);
// start render loop
Thread loop = new Thread(new MainLoop());
loop.start();
}
}
Your painting on my machine is rather smooth but it is burning a lot of performance in that loop, but on a slower machine I could imagine that the animation is jumpy if your application is busy executing the while or handling the paint events instead of rendering.
It may be better to update the position based on how much time has elapsed per render, and I am unsure how accurate it is to compare times for animation purposes through Date objects, so comparing small time differences using System.nanoTime() may be better. For example:
long currentNanoTime = System.nanoTime();
long timeElapsed = currentNanoTime - lastUpdateNanoTime;
lastUpdateNanoTime = currentNanoTime;
...
int newXPosition = oldXPosition + (velocityXInPixelsPerNanoSecond * timeElapsed);
I am trying to develop a game in which I need to draw a grid. For that I am using the paintComponent(Graphics g) method which is being called by repaint() method.
The problem is that the repaint method is inside the infinite While loop and it never calls the paintComponent() method unless I minimize and maximize the screen. After that it works fine and calls the paintComponent() perfectly in the while loop.
So in short, I need to trigger it by Minimizing-Maximizing the screen.
Can anybody help me out?
In the code you can see 3 classes namely, Frame.java, MenuHandler.java & Screen.java. Code starts from the main method in Frame class and it adds the Screen class to itself as Screen extends JPanel. However, the control goes to the Screen class only when user selects "Create Map" on the menu. Then MenuHandler class passes the control to the Screen class where the run method is called in the createMap method and I invoking the repaint method inside this run method.
package so;
public class Screen extends JPanel implements Runnable {
Frame frame;
public Screen(Frame frame) {
this.frame = frame;
}
public void createMap() {
thread.start();
}
public void paintComponent(Graphics g) {
g.setColor(Color.BLUE);
}
#Override
public void run() {
System.out.println("Success******");
long lastFrame = System.currentTimeMillis();
int frames = 0;
running = true;
scene = 0;
// the map grid would be refreshed every 2 ms so that we don't get the
// flickering effect
while (running) {
frames++;
if (System.currentTimeMillis() - 1000 >= lastFrame) {
fps = frames;
frames = 0;
lastFrame = System.currentTimeMillis();
}
// to draw stuff all the time on the screen : goes around 2 millions
// frames per second. Which is of no use.
repaint();
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.exit(0);
}
}
Timer code in the run method:
public void run() {
System.out.println("Success");
long lastFrame = System.currentTimeMillis();
int frames = 0;
running = true;
scene = 0;
// the map grid would be refreshed every 2 ms so that we don't get the
// flickering effect
while (running) {
frames++;
if (System.currentTimeMillis() - 1000 >= lastFrame) {
fps = frames;
frames = 0;
lastFrame = System.currentTimeMillis();
}
System.out.println("before repaint");
// to draw stuff all the time on the screen : goes around 2 millions
// frames per second. Which is of no use.
ActionListener taskPerformer = new ActionListener() {
public void actionPerformed(ActionEvent evt) {
repaint();
}
};
new Timer(200, taskPerformer).start();
System.out.println("after repaint");
try {
Thread.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.exit(0);
}
I figured out the issue. it was just one line that I had to add to trigger the paintComponent method instead of doing it by minimizing-maximizing the window.
Frame was my top level container and I was adding Screen component (that extends JPanel and has the implementation of paintComponent) to the frame. So while adding, earlier I was doing
frame.add(screen);
I changed this to:
frame.getContentPane().add(screen);
frame.getContentPane().validate();
Calling the validate method after adding it did it for me. I don't know if it makes sense but yes that was the only line that worked for me.
Hope it helps.
There is a problem with the repaint() method in Java. I made a new thread that constantly repaints the screen. When I release the spacebar I want my player to fall smoothly by setting its position and then waiting for 50 milliseconds and looping that 20 times. Instead, it waits the whole amount of time in the loop, then repaints. I am wondering why it doesn't constantly repaint the changes in the players co-ordinates. Thank you.
(Edit) Thanks everyone for the help. This is my first time using stack overflow, and I am only 13 and still learning java, so I probably will go back to the tutorials again.
My 'a' class (main):
public class a {
public static void main(String[] args) {
JFrame frame = new JFrame("StickFigure Game");
frame.setSize(740, 580);
frame.setDefaultCloseOperation(frame.EXIT_ON_CLOSE);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
b board = new b();
frame.add(board);
frame.addKeyListener(board);
}
}
My 'b' class (JPanel/drawing):
public class b extends JPanel implements KeyListener {
c player = new c();
public class MyRunnable implements Runnable {
public void run() {
while (true)
repaint();
}
}
MyRunnable run = new MyRunnable();
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D) g;
g2d.drawImage(player.getImage(), player.getX(), player.getY(), 80, 140,
null);
}
public b() {
Thread thread = new Thread(new MyRunnable());
thread.start();
}
public static void slow(int n) {
long t0, t1;
t0 = System.currentTimeMillis();
do {
t1 = System.currentTimeMillis();
} while (t1 - t0 < n);
}
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_D) {
player.setPos(player.getX() + 6, player.getY());
}
if (e.getKeyCode() == KeyEvent.VK_A) {
player.setPos(player.getX() - 6, player.getY());
}
if (e.getKeyCode() == KeyEvent.VK_SPACE) {
player.setPos(player.getX(), player.getY() - 60);
}
}
public void keyReleased(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_SPACE) {
for (int i = 0; i < 20; i++) {
slow(50);
player.setPos(player.getX(), player.getY() + 2);
}
}
}
public void keyTyped(KeyEvent e) {
}
}
my 'c' class (player):
public class c {
private ImageIcon i = new ImageIcon("guy.png");
private Image img = i.getImage();
private int x = 0;
private int y = 100;
public void wait(int what) {
try {
Thread.sleep(what);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public c() {
}
public Image getImage() {
return img;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public void setPos(int mx, int my) {
x = mx;
y = my;
}
}
I haven't gone through all the code here but here are some pointers:
Swing has its own concurrency mechanisms which allow you to handle UI updates. You can use a Swing Timer rather than a raw Thread. Related is the use of Thread.sleep - don't do this, it only blocks the EDT and prevents UI updates.
The Swing paint chain mechanism requires you to override paintComponent rather than paint.
Always use Key Bindings rather than KeyListeners in Swing. KeyListeners require component focus to work to interact with the KeyEvents. Key Bindings do not have this limitation.
"There is a problem with the repaint() method in java." Did you consider that perhaps the problem is with your code instead? You are blocking the event thread and giving the system no time to do the intermediate repaints. In particular, this method:
public static void slow (int n){
long t0,t1;
t0=System.currentTimeMillis();
do{
t1=System.currentTimeMillis();
}
while (t1-t0<n);
}
and this loop:
for(int i = 0;i<20;i++){
slow(50);
player.setPos(player.getX(), player.getY()+2);
}
do not relinquish control to the system so that repaints can actually happen. Rewrite those using Swing timers. Look at this tutorial for an introduction on how to use these.
Also, your thread that constantly calls repaint() in a tight loop:
public void run(){
while(true) repaint();
}
is a terrible idea. You don't need to call repaint() at full CPU speed. Once every 30 milliseconds or so is fine for animation. Again, consider using Swing utilities to do this rather than writing your own looping thread.
The repaint is only a "request" to paint as soon as possible. so when you call it it causes a call to the paint method as soon as possible.
from here
So basically you just flooding the scheduled calls of paint or update with while(true) repaint();.
Oracle's stance on painting in AWT and Swing
One way you could do it, or should I say how I would do it, is to make your c class implement KeyListener, so that when a key is pressed (and only when it is pressed) you update it's location.
So move your KeyListener methods to class c, in your class b constructor you can add the call this.addKeyListener(player) or make a method void addPlayer(c player) that adds it.
I'm making a simple tower defense game in Swing and I've run into a performance problem when I try to put many sprites (more than 20) on screen.
The whole game takes place on a JPanel which has setIgnoreRepaint(true).
Here is the paintComponent method (con is the Controller):
public void paintComponent(Graphics g){
super.paintComponent(g);
//Draw grid
g.drawImage(background, 0, 0, null);
if (con != null){
//Draw towers
for (Tower t : con.getTowerList()){
t.paintTower(g);
}
//Draw targets
if (con.getTargets().size() != 0){
for (Target t : con.getTargets()){
t.paintTarget(g);
}
//Draw shots
for (Shot s : con.getShots()){
s.paintShot(g);
}
}
}
}
The Target class simple paints a BufferedImage at its current location. The getImage method doesn't create a new BufferedImage, it simply returns the Controller class's instance of it:
public void paintTarget(Graphics g){
g.drawImage(con.getImage("target"), getPosition().x - 20, getPosition().y - 20, null);
}
Each target runs a Swing Timer to calculate its position. This is the ActionListener it calls:
public void actionPerformed(ActionEvent e) {
if (!waypointReached()){
x += dx;
y += dy;
con.repaintArea((int)x - 25, (int)y - 25, 50, 50);
}
else{
moving = false;
mover.stop();
}
}
private boolean waypointReached(){
return Math.abs(x - currentWaypoint.x) <= speed && Math.abs(y - currentWaypoint.y) <= speed;
}
Other than that, repaint() is only called when placing a new tower.
How can I improve the performance?
Each target runs a Swing Timer to calculate its position. This is the ActionListener it calls:
This may be your problem - having each target/bullet (I assume?) responsible for keeping track of when to update itself and draw itself sounds like quite a bit of work. The more common approach is to have a loop along the lines of
while (gameIsRunning) {
int timeElapsed = timeSinceLastUpdate();
for (GameEntity e : entities) {
e.update(timeElapsed);
}
render(); // or simply repaint in your case, I guess
Thread.sleep(???); // You don't want to do this on the main Swing (EDT) thread though
}
Essentially, an object further up the chain has the responsibility to keep track of all entities in your game, tell them to update themselves, and render them.
I think what might be at fault here is your whole logic of the games setup (no offense intended), As stated in another answer you have different timers taking care of each entities movement, this is not good. I'd suggest taking a look at some gaming loop examples, and adjusting yours to this, you'll notice a great readability and performance improvement a few nice links:
http://www.java-gaming.org/index.php/topic,24220.0
http://www.cokeandcode.com/info/tut2d.html
http://entropyinteractive.com/2011/02/game-engine-design-the-game-loop/
I was initially wary of the too-many-timer theory. Instances of javax.swing.Timer use "a single, shared thread (created by the first Timer object that executes)." Dozens or even scores are perfectly fine, but hundreds typically start to become sluggish. Depending on period and duty cycle, the EventQueue eventually saturates. I agree with the others that you need to critically examine your design, but you may want to experiment with setCoalesce(). For reference, here's an sscce that you may like to profile.
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.Timer;
/**
* #see http://stackoverflow.com/a/11436660/230513
*/
public class TimerTest extends JPanel {
private static final int N = 25;
public TimerTest() {
super(new GridLayout(N, N));
for (int i = 0; i < N * N; i++) {
this.add(new TimedLabel());
}
}
private static class TimedLabel extends JLabel {
private static final Random r = new Random();
public TimedLabel() {
super("000", JLabel.CENTER);
// period 100 to 1000 ms; frequency 1 to 10 Hz.
Timer timer = new Timer(r.nextInt(900) + 100, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
TimedLabel.this.setText(next());
}
});
timer.setCoalesce(true);
timer.start();
}
private String next() {
return String.valueOf(r.nextInt(900) + 100);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(640, 480);
}
private void display() {
JFrame f = new JFrame("TimerTet");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(new JScrollPane(this));
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
new TimerTest().display();
}
});
}
}
for painting in the Swing is better (in all cases >= Java5) use Swing Timer exclusivelly
this painting proccess required only one Swing Timer
example about bunch of Stars and one Swing Timer
Try to use one timer for all the targets.
If you have 20 targets then you will also have 20 timers running simultaneously (think about 1000 targets?). There is some expense and the most important thing is each of them is doing the similar job -- to calculate the position -- You don't need to split them. I guess it is a simple task, which will not take you a blink, even running 20 times.
If I got the point, What you want to do is trying to change the positions of all the targets at the same time. You can achieve this by changing all of them in one single method running in one thread.