first post here ever so forgive me if I am totally ignorant of all the rules.
I have some issues, I am relatively new to Java and have read and got some help from this community before.
I am having issues at the moment paint multiple balls on a JFrame, I have some solutions from other students but to no success. One student has got it working now but by painting everything within the Frame class which I don't feel is correct and putting repaint() within paint() which also feels wrong. if anyone could point me in the right direction I would be extremely appreciative.
Daniel
Code:
Gamejava
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package myanimie;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import javax.swing.JFrame;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
/**
*
* #author Dan
*/
public class Game extends JFrame implements Runnable {
private Ball myBall = new Ball();
private Paddle myPad = new Paddle();
final JPanel jp = new JPanel();
final JPanel jp1 = new JPanel();
final JPanel jp2 = new JPanel();
public Game()
{
setVisible(true);
setResizable(false);
setTitle("First Test Animation");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setAlwaysOnTop(true);
setSize(640,480);
}
public void run()
{
move();
}
public void paint(Graphics g)
{
super.paint(g);
myBall.paint(g);
}
public void move()
{
myBall.start();
repaint();
try
{
Thread.sleep(50);
}
catch (InterruptedException e)
{
System.exit(0);
}
}
}
Ball.java
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package myanimie;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.geom.Ellipse2D;
/**
*
* #author Dan
*/
public class Ball extends Thread {
Point pos;
Color ballColor = Color.red;
Color[] ts = {Color.CYAN,Color.green,Color.black};
private int yChange = 2;
private int xChange = 1;
public Ball()
{
pos = new Point();
pos.x = (int)(Math.random() * (500 - 100)) + 10 ;
pos.y = (int)(Math.random() * (500/2 - 100)) + 10;
}
#Override
public void run()
{
while(true)
{
move();
}
}
public void paint(Graphics g)
{
g.setColor(ballColor);
g.fillOval(pos.x - 10, pos.y - 10, 60,60);
}
public void move()
{
// System.out.println("y " + pos.y);
// System.out.println("x " + pos.x);
if(pos.y < 20)
{
yChange = -yChange;
System.out.println("T");
ballColor = Color.BLUE;
}
if(pos.x < 20)
{
xChange = -xChange;
System.out.println("L");
ballColor = Color.MAGENTA;
}
if(pos.x > 620 - 20)
{
xChange = -xChange;
System.out.println("R");
ballColor = Color.GREEN;
}
if(pos.y > 430 - 20)
{
yChange = -yChange;
System.out.println("B");
ballColor = Color.PINK;
}
if(pos.y < 640 - 20)
{
pos.translate(xChange, yChange);
}
if(pos.x < 480 - 20 || pos.x > 460)
{
pos.translate(xChange, yChange);
}
}
public Point getPosition()
{
return pos;
}
public Ellipse2D area()
{
return new Ellipse2D.Double(pos.x, pos.y,60,60);
}
}
This is my terrible code, I have gotten around the errors but no animation atm.
Thanks guys!! your insight is invaluable
"and putting repaint() within paint()" sounds dangerous!
repaint() method causes a call to this component's paint method as soon as possible.
You may elaborate how you are trying to draw "multiple" balls on the frame. If nothing special, your answer may be here
OK some general observations based on the code you posted.
You need to throttle your move() method so that it's not updating millions of times per second. I see you have a Thread.sleep() in your Game.move method. I think what you were trying to do was something like this:
public void run() {
while(true) {
//change the game state
move();
//draw the changes to the state that I just made
repaint();
//wait before moving to the next "frame"
try {
Thread.sleep(50);
} catch ( InterruptedException ie ) {
}
}
}
private void move() {
myBall.move();
}
Since Ball.move() won't loop anymore, don't have Ball extend Thread at all. Get rid of the run() method. Your main game loop will be managed centrally by Game.
From your main method or whoever starts the game running, call Game.run() either in that thread or in a new thread like this:
public void main(String[] args) {
Game game /* = new Game(...)*/;
Thread gameThread = new Thread(game);
gameThread.start();
}
Is there a good reason for getPosition() to exist? This is probably better off as encapsulated state.
Yes it is indeed very bad practice. Calling repaint() in paint() will cause a StackOverflow Error.
Take a look at the game engine Bonsai from Ivo Wetzel. I like it a lot.
And indeed: You should create a JComponent which overrides the paintComponent(Graphics g) method. Add that JComponent to the JFrame. But you don't have to care about this. This will do the Bonsai game engine automatically.
Related
I am trying to draw a circle around a point while allowing me to still manually move the cursor. I figured out how to draw the circle around a point but if I bump my mouse a little bit it screws up big time. I know that if I move the mouse a bit while making the circle it won't likely come out as a perfect circle but that's fine. My goal is to be able to drag the mouse around with it constantly trying to make circles around that moving point. Here is the code I have so far (I have tried multiple iterations).
int radius = 100;
for (double i = 0; i < (2 * Math.PI) + Math.PI / 6; i = i + Math.PI / 6) {
PointerInfo pointerA = MouseInfo.getPointerInfo();
Point a = pointerA.getLocation();
int yStart = (int) a.getY();
int xStart = (int) a.getX();
robot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
robot.mouseMove((int) ((xStart) + (radius * Math.cos(i))), (int) ((yStart) + (radius * Math.sin(i))));
robot.delay(68);
}
This is what it looks like when I don't move my cursor at all...
This is what it looks like when I move my cursor a bit...
I also know that this will only make one circle but I can't figure out how to run this code when my nativeMousePressed event occurs and then stop it when the nativeMouseReleased event occurs. I tried to run the code above in a method that contains a while loop but that did not work. I assigned a boolean to true when the mouse is clicked and then assigned it to false when the mouse button is released but I think the while loop was working on a different thread or something because none of the code would work besides from the infinite while loop. Needless to say, I removed the while loop, for now, to try and figure out why the circle was not printing right :/
Here is a little edit since I may not have made what I am trying to do clear. I am trying to write code that creates circles while allowing me to move my mouse around the screen (I don't want it to recenter). Now assuming I moved my cursor to the right and the code ran in a while loop instead of only creating 1 circle it should look something like this. (I also need to figure out why all those lines appeared because I even printed the locations which ended up looking nearly the same as when I did not move the mouse).
This problem is caused by the 68ms delay in which the user is free to control the mouse. In the ideal case, you would use JNI or JNA to temporarily disable the mouse on an OS level. That however comes with its own challenges, including that you might lock yourself out while testing. Since you are okay with a bit of jitter, there is an easier alternative. Instead of waiting 68ms, you could spam-place the mouse to the desired location, decreasing the time a user has to move the mouse before it gets set again. You can do that as follows:
for(int i = 0; i < 68; i++) {
robot.mouseMove(...);
robot.delay(1);
}
Or if the 68ms has to be somewhat precise:
long startTime = System.currentTimeMillis();
while(System.currentTimeMillis() - startTime < 68) {
robot.mouseMove(...);
robot.delay(1);
}
You can play around with the delay to see what happens. A zero delay might be heavy on the CPU.
This is a really basic demonstration.
The "problem" here is, the MouseMotionListener can't tell the difference between the Robot moving the mouse or the user moving the mouse. So, if you move the mouse while it's drawing, it will add those points as well (you can see it demonstrated below)
One thing I did note was the fact that you never release the mouse button, so it will continue to trigger mouseDragged events to the underlying window/canvas even after your loop has completed.
import java.awt.AWTException;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.MouseInfo;
import java.awt.Point;
import java.awt.PointerInfo;
import java.awt.Robot;
import java.awt.event.ActionEvent;
import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.AbstractAction;
import javax.swing.ActionMap;
import javax.swing.InputMap;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.KeyStroke;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
JFrame frame = new JFrame();
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
} catch (AWTException ex) {
Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
}
}
});
}
public class TestPane extends JPanel {
private List<List<Point>> points = new ArrayList<List<Point>>(25);
private List<Point> activeList;
private Robot bot;
volatile private boolean isRunning = false;
public TestPane() throws AWTException {
bot = new Robot();
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
if (!isRunning) {
return;
}
System.out.println("...mousePressed");
activeList = new ArrayList<>(25);
points.add(activeList);
}
#Override
public void mouseReleased(MouseEvent e) {
if (!isRunning) {
return;
}
System.out.println("...mouseReleased");
activeList = null;
}
});
addMouseMotionListener(new MouseAdapter() {
#Override
public void mouseDragged(MouseEvent e) {
if (!isRunning) {
return;
}
System.out.println("...mouseDragged");
activeList.add(e.getPoint());
repaint();
}
#Override
public void mouseMoved(MouseEvent e) {
if (!isRunning) {
return;
}
System.out.println("...mouseMoved");
}
});
InputMap im = getInputMap(WHEN_IN_FOCUSED_WINDOW);
ActionMap am = getActionMap();
am.put("start", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
new Thread(new Runnable() {
#Override
public void run() {
isRunning = true;
drawCircle();
isRunning = false;
}
}).start();
}
});
im.put(KeyStroke.getKeyStroke(KeyEvent.VK_SPACE, 0), "start");
}
#Override
public Dimension getPreferredSize() {
return new Dimension(400, 400);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (points.isEmpty()) {
return;
}
Graphics2D g2d = (Graphics2D) g.create();
for (List<Point> sequence : points) {
List<Point> copy = new ArrayList<>(sequence);
Point last = copy.remove(0);
for (Point next : copy) {
g2d.drawLine(last.x, last.y, next.x, next.y);
last = next;
}
}
g2d.dispose();
}
protected void drawCircle() {
int radius = 100;
PointerInfo pointerA = MouseInfo.getPointerInfo();
Point a = pointerA.getLocation();
bot.mousePress(InputEvent.BUTTON1_DOWN_MASK);
int xStart = (int) a.getX();
int yStart = (int) a.getY();
bot.delay(5);
for (double i = 0; i < (2 * Math.PI) + Math.PI / 6; i = i + Math.PI / 6) {
int xPos = (int) ((xStart) + (radius * Math.cos(i)));
int yPos = (int) ((yStart) + (radius * Math.sin(i)));
System.out.println(xPos + "x" + yPos);
bot.mouseMove(xPos, yPos);
bot.delay(5);
}
bot.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
bot.delay(5);
bot.mouseMove(xStart, yStart);
}
}
}
So, you problem is a little understated in it's context/intent, so it's difficult to really make a suitable suggestion, but remember, you're not dealing with an actual "API" you can inject information into, you're dealing with posting events in the system event queue which are then been consumed by the window which currently has focus
This is an UI that makes a ball go down in a diagonal way, but the ball stays static; it seems something is not working adecuatedly with the threads. Could you please, tell me how to make the ball move?
Please download a ball and change the directory so the program can find where your ball is allocated. It's not necessary to download the soccer pitch but if you want, it's OK. Finally, I have to thank you for spending time in search of this malfunctioning.
import java.awt.BorderLayout;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JButton;
import java.awt.event.ActionListener;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import javax.imageio.ImageIO;
import java.io.File;
class Animation extends JFrame implements ActionListener { //Frame and listener
Rectangle2D dimensions = new Rectangle2D.Double(0,0,850,595); //Not implemented limits
JButton animate, stop;
Runnable runnable;
Thread move;
public Animation() {
setLayout(new BorderLayout()); //BorderLayout disposition
setTitle("Pelota en acción");
animate = new JButton("Animate it!"); //Button to create balls
animate.setBounds(0,0,120,30);
animate.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
Image ball = null;
new Layout().createEllipse(ball);
runnable = new Layout();
move = new Thread(runnable);
move.start();
}
});
stop = new JButton("Freeze"); //Button to interrupt thread (not implemented)
stop.setBounds(0,0,120,30);
stop.addActionListener(new ActionListener(){
#Override
public void actionPerformed(ActionEvent e) {
move.interrupt();
Layout.running = false;
}
});
JPanel subPanel = new JPanel(); //Layout with its buttons situated to the south
subPanel.add(animate);
subPanel.add(stop);
add(subPanel,BorderLayout.SOUTH);
add(new Layout());
}
public static void main(String[] args) {
Animation ventana = new Animation();
ventana.setSize(850,625);
ventana.setLocationRelativeTo(null);
ventana.setVisible(true);
ventana.setDefaultCloseOperation(EXIT_ON_CLOSE);
}
#Override
public void actionPerformed(ActionEvent e) {} //Tag
} //Class close
class Layout extends JPanel implements Runnable { //Layout and thread
int X,Y; //Coordenadas
static boolean running = true; //"To interrupt the thread" momentaneously.
static ArrayList<Image> balls = new ArrayList<>(); //Balls collection
#Override
public void run () { //Just moves ball towards Narnia xd
while(running) {
X++; Y++;
System.out.println(X+" "+Y);
repaint();
updateUI();
try {
Thread.sleep(4);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
g2.addRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
repaint();
updateUI();
try {
URL url = new URL("https://www.freejpg.com.ar/image-900/9c/9ca2/F100004898-textura_pasto_verde_linea_de_cal.jpg");
Image picture = ImageIO.read(url);
g.drawImage(picture,0,0,null);
} catch(IOException e){
System.out.println("URL image was not found");
}
finally {
try {
//----------------------------------------------------------------------------
Image picture = ImageIO.read(new File("C:\\Users\\Home\\Desktop\\Cancha.jpg")); //Pitch
//----------------------------------------------------------------------------
g.drawImage(picture, 0, 0, null);
} catch (IOException ex) {
System.out.println("Pitch image was not found");
}
}
for (Image ball : balls) { //I add balls to the Layout
g2.drawImage(ball,X,Y,100,100,null);
}
}
public void createEllipse (Image ball) { //Method that adds balls to the collection
try {
//-------------------------------------------------------------------- Ball
ball = ImageIO.read(new File("C:\\Users\\Home\\Desktop\\Pelota.png")); //Change this
//-------------------------------------------------------------------- Ball
} catch(IOException ex) {
System.out.println("Any balls were found");
}
balls.add(ball);
}
}
So to break your code down:
When the button is pressed, you execute the following code:
Image ball = null;
new Layout().createEllipse(ball);
runnable = new Layout();
move = new Thread(runnable);
move.start();
This will create a new layout. The run() method of this will increase the X and Y variables. They are declared here:
int X,Y; //Coordenadas
Those are instance variables, this means they belong to your newly created Layout.
Then you call repaint() on the new Layout, which will do nothing, because this new Layout has not been added to some window.
So, how do you fix this?
First, you have to keep the original Layout around:
class Animation extends JFrame { // no need to implement ActionListener
Rectangle2D dimensions = new Rectangle2D.Double(0,0,850,595); //Not implemented limits
JButton animate, stop;
Thread move;
Layout layout;
Then remember the Layout when you create it:
// before: add(new Layout());
layout = new Layout();
add(layout);
Then use the layout in your ActionListener:
layout.createEllipse(ball);
move = new Thread(layout);
move.start();
This might have some problems with concurrency (Swing is not thread-safe), so for good measure, you should call repaint() in the AWTEventThread:
// in run(), was repaint():
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
repaint();
}
});
Now, there are some cleanup tasks left:
Delete this code:
#Override
public void actionPerformed(ActionEvent e) {} //Tag
It's no longer needed, because you don't implement ActionListener.
Drop the static modifiers from some fields, and add volatile:
volatile int X,Y; //Coordenadas
volatile boolean running = true; //"To interrupt the thread" momentaneously.
ArrayList<Image> balls = new ArrayList<>(); //Balls collection
volatile is needed for variables that are accessed from more than one thread.
Also remove repaint() and resetUI() from the paint method. You don't need them.
For the pictures in paint: you should cache them. Store them in a field, so you don't have to load the picture every time.
When all this is done, your code is much cleaner, but there are still some warts that should be addressed. But at least you have something working.
Johannes has already spoken about many of the things which are wrong with your original example, so I won't go over many of them again.
This example makes use of a Swing Timer instead of a Thread as the main "animation" loop. It also focuses on demonstrating encapsulation and responsibility.
For example, the AnimtionPane is responsible for managing the balls, managing the animation loop and paint. It isn't, however, responsible for determining "how" the balls are updated or paint, it only provides the timing and functionality to make those things happen.
A couple of the glaring issues I can see are:
Trying to load resources from within the paintComponent method. This is a bad ideas, as it could slow you paint pass down, causing your UI to lag
Calling repaint and updateUI from within the paintComponent method. You should avoid causing any new updates to the UI from occurring during a paint process. This could cause your program to run wide and consume all the CPU cycles, not only making your app non-responsive, but also the whole system.
Some very quick points
Swing is not thread safe. You should never update the UI (or anything the UI relies on) from outside the context of the Event Dispatching Thread. This example uses a Swing Timer as it allows the delay to occur of the EDT (and not block the UI), but it's updates are triggered within the EDT, allowing us to safely update the UI from within
You create multiple instances of Layout, meaning that the one on the screen isn't the one which is been updated
Your "freeze" logic is broken. It will never "freeze" anything
Runnable example
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.Rectangle;
import java.awt.RenderingHints;
import java.awt.Shape;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
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);
}
});
}
public class TestPane extends JPanel {
private AnimationPane animationPane;
public TestPane() {
setLayout(new BorderLayout());
animationPane = new AnimationPane();
JButton actionButton = new JButton("Start");
actionButton.addActionListener(new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
if (animationPane.isAnimating()) {
animationPane.stop();
actionButton.setText("Start");
} else {
animationPane.start();
actionButton.setText("Stop");
}
}
});
add(animationPane);
add(actionButton, BorderLayout.SOUTH);
}
}
// This is just makes it seem more random ;)
private static Random RANDOM = new Random();
public class Ball {
private int x;
private int y;
private int xDelta;
private int yDelta;
private Color color;
private Shape shape;
public Ball(Color color) {
shape = new Ellipse2D.Double(0, 0, 10, 10);
this.color = color;
// Get some random motion
do {
xDelta = RANDOM.nextInt(6) + 2;
yDelta = RANDOM.nextInt(6) + 2;
} while (xDelta == yDelta);
}
public void update(Rectangle bounds) {
x += xDelta;
y += yDelta;
if (x + 10 > bounds.x + bounds.width) {
x = bounds.x + bounds.width - 10;
xDelta *= -1;
} else if (x < bounds.x) {
x = bounds.x;
xDelta *= -1;
}
if (y + 10 > bounds.y + bounds.height) {
y = bounds.y + bounds.height - 10;
yDelta *= -1;
} else if (y < bounds.y) {
y = bounds.y;
yDelta *= -1;
}
}
public void paint(Graphics2D g2d) {
// This makes it easier to restore the graphics context
// back to it's original state
Graphics2D copy = (Graphics2D) g2d.create();
copy.setColor(color);
copy.translate(x, y);
copy.fill(shape);
// Don't need the copy any more, get rid of it
copy.dispose();
}
}
public class AnimationPane extends JPanel {
// This does not need to be static
private List<Ball> balls = new ArrayList<>(); //Balls collection
private Timer timer;
private List<Color> colors;
public AnimationPane() {
colors = new ArrayList<>(8);
colors.add(Color.RED);
colors.add(Color.GREEN);
colors.add(Color.BLUE);
colors.add(Color.CYAN);
colors.add(Color.MAGENTA);
colors.add(Color.ORANGE);
colors.add(Color.PINK);
colors.add(Color.YELLOW);
timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent evt) {
if (RANDOM.nextBoolean()) {
makeBall();
}
Rectangle bounds = new Rectangle(new Point(0, 0), getSize());
for (Ball ball : balls) {
ball.update(bounds);
}
repaint();
}
});
makeBall();
}
protected void makeBall() {
Collections.shuffle(colors);
balls.add(new Ball(colors.get(0)));
}
public boolean isAnimating() {
return timer.isRunning();
}
public void start() {
timer.start();
}
public void stop() {
timer.stop();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g.create();
g2.addRenderingHints(new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON));
// Bad ideas. Repaint will cause a new paint event to be posted, causing your
// UI to run away - consuming all your CPU cycles in a singulator forms
// and destorys the known universe
//repaint();
// This doesn't do what you think it does and there shouldn't be
// reason for you to call it
//updateUI();
// This is a bad idea as it could cause the paint cycles to slow down
// destorying the responsiveness of your app
// Besids, you should be passing this as the ImageObserver
// try {
// URL url = new URL("https://www.freejpg.com.ar/image-900/9c/9ca2/F100004898-textura_pasto_verde_linea_de_cal.jpg");
// Image picture = ImageIO.read(url);
// g.drawImage(picture, 0, 0, null);
// } catch (IOException e) {
// System.out.println("URL image was not found");
// } finally {
// try {
// //----------------------------------------------------------------------------
// Image picture = ImageIO.read(new File("C:\\Users\\Home\\Desktop\\Cancha.jpg")); //Pitch
// //----------------------------------------------------------------------------
// g.drawImage(picture, 0, 0, null);
// } catch (IOException ex) {
// System.out.println("Pitch image was not found");
// }
// }
// This is "bad" per say, but each ball should have it's own
// concept of location
// for (Image ball : balls) { //I add balls to the Layout
// g2.drawImage(ball, X, Y, 100, 100, null);
// }
for (Ball ball : balls) {
ball.paint(g2);
}
// I made a copy of the graphics context, as this is shared
// with all the other components been painted, changing the
// render hints could cause issues
g2.dispose();
}
}
}
Even after using Java Swing for over a year, it still seems like magic to me. How do I correctly use a BufferStrategy, in particular, the method createBufferSrategy()?
I would like to have a JFrame and a Canvas that gets added to it and then painted. I would also like to be able to resize (setSize()) the Canvas. Every time I resize the Canvas it seems my BufferStrategy gets trashed or rather, turns useless, since using show() on the BufferStrategy does not actually do anything. Also, createBufferStrategy() has a weird non-deterministic behaviour and I don't know how to synchronize it correctly.
Here's what I mean:
public class MyFrame extends JFrame {
MyCanvas canvas;
int i = 0;
public MyFrame() {
setUndecorated(false);
setVisible(true);
setSize(1100, 800);
setLocation(100, 100);
setDefaultCloseOperation(EXIT_ON_CLOSE);
canvas = new MyCanvas();
add(canvas);
canvas.makeBufferStrat();
}
#Override
public void repaint() {
super.repaint();
canvas.repaint();
//the bigger threshold's value, the more likely it is that the BufferStrategy works correctly
int threshold = 2;
if (i < threshold) {
i++;
canvas.makeBufferStrat();
}
}
}
MyCanvas has a method makeBufferStrat() and repaint():
public class MyCanvas extends Canvas {
BufferStrategy bufferStrat;
Graphics2D g;
public MyCanvas() {
setSize(800, 600);
setVisible(true);
}
public void makeBufferStrat() {
createBufferStrategy(2);
//I'm not even sure whether I need to dispose() those two.
if (g != null) {
g.dispose();
}
if (bufferStrat != null) {
bufferStrat.dispose();
}
bufferStrat = getBufferStrategy();
g = (Graphics2D) (bufferStrat.getDrawGraphics());
g.setColor(Color.BLUE);
}
#Override
public void repaint() {
g.fillRect(0, 0, 100, 100);
bufferStrat.show();
}
}
I simply call MyFrame's repaint() method from a while(true) loop in the main method.
When threshold is small (i.e. 2), bufferStrat.show() in about 70% of all cases doesn't do anything - the JFrame just remains gray upon starting the program. The remaining 30% it paints the rectangle how it's supposed to. If I do threshold = 200;, the painting succeeds close to 100% of the time I execute the program. Javadoc says that createBufferStrategy() may take a while, so I assume that's the issue here. However, how do I synchronize and use it properly? Clearly, I'm doing something wrong here. I can't imagine that's how it's supposed to be used.
Does anyone have a minimal working example?
The way you create the BufferStrategy is "okay", you could have a look at the JavaDocs for BufferStrategy which has a neat little example.
The way you're using it, is questionable. The main reason for using a BufferStrategy is because you want to take control of the painting process (active painting) away from Swing's painting algorithm (which is passive)
BUT, you seem to trying to do both, which is why it's causing your issues. Instead, you should have a "main" loop which is responsible for deciding what and when the buffer should paint, for example...
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferStrategy;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JFrame;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
TestPane testPane = new TestPane();
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(testPane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
// The component needs to be attached to displayed window before
// the buffer can be created
testPane.startPainting();
}
});
}
public class TestPane extends Canvas {
private AtomicBoolean painting = new AtomicBoolean(true);
private PaintCycle paintCycle;
private Rectangle clickBounds;
public TestPane() {
addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
if (clickBounds != null && clickBounds.contains(e.getPoint())) {
painting.set(false);
}
}
});
}
public void startPainting() {
if (paintCycle == null) {
createBufferStrategy(2);
painting.set(true);
paintCycle = new PaintCycle();
Thread t = new Thread(paintCycle);
t.setDaemon(true);
t.start();
}
}
public void stopPainting() {
if (paintCycle != null) {
painting.set(false);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
public class PaintCycle implements Runnable {
private BufferStrategy strategy;
private int xDelta = 2;
private int yDelta = 2;
#Override
public void run() {
System.out.println("Painting has started");
int x = (int) (Math.random() * (getWidth() - 40));
int y = (int) (Math.random() * (getHeight() - 40));
do {
xDelta = (int) (Math.random() * 8) - 4;
} while (xDelta == 0);
do {
yDelta = (int) (Math.random() * 8) - 4;
} while (yDelta == 0);
clickBounds = new Rectangle(x, y, 40, 40);
strategy = getBufferStrategy();
while (painting.get()) {
// Update the state of the model...
update();
// Paint the state of the model...
paint();
try {
// What ever calculations you want to use to maintain the framerate...
Thread.sleep(40);
} catch (InterruptedException ex) {
}
}
System.out.println("Painting has stopped");
}
protected void update() {
int x = clickBounds.x + xDelta;
int y = clickBounds.y + yDelta;
if (x + 40 > getWidth()) {
x = getWidth() - 40;
xDelta *= -1;
} else if (x < 0) {
x = 0;
xDelta *= -1;
}
if (y + 40 > getHeight()) {
y = getHeight() - 40;
yDelta *= -1;
} else if (y < 0) {
y = 0;
yDelta *= -1;
}
clickBounds.setLocation(x, y);
}
protected void paint() {
// Render single frame
do {
// The following loop ensures that the contents of the drawing buffer
// are consistent in case the underlying surface was recreated
do {
// Get a new graphics context every time through the loop
// to make sure the strategy is validated
Graphics2D graphics = (Graphics2D) strategy.getDrawGraphics();
// Render to graphics
// ...
graphics.setColor(Color.BLUE);
graphics.fillRect(0, 0, getWidth(), getHeight());
graphics.setColor(Color.RED);
graphics.fill(clickBounds);
// Dispose the graphics
graphics.dispose();
// Repeat the rendering if the drawing buffer contents
// were restored
} while (strategy.contentsRestored());
// Display the buffer
strategy.show();
// Repeat the rendering if the drawing buffer was lost
} while (strategy.contentsLost());
}
}
}
}
You should also remember, Swing's been using either DirectX or OpenGL pipelines since about 1.4 (or maybe 1.5). The main reasons for using BufferStrategy are more direct access to the hardware (which Swing is pretty close to anyway) AND direct control over the painting process (which is now really the only reason to use it)
How do I make a sprite move in a custom JPanel?
I have looked at the similar questions and although one question is similar, it isn't addressing my problem. I have a sprite in a JPanel and I am unable to get it to move. One of the requirements I have to meet for the program is that it must begin moving when a JButton is pressed (Mouse Click). I have the code set-up in a way I believe should work, but it will spit out a long list of errors when I press the button. I'm also required to have the panel be a custom panel class.
What I need to know is this:
Methods (ha) of programming sprite movement.
Continuing to move the sprite without a trail.
Making the sprite bounce off the edges of the panel. Done (Unable to test due to no moving ball)
Here's the code I have (MainClient).
package clientPackage;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JFrame;
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import logicPack.Logic;
import javax.swing.JButton;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class ClientClass
{
Ball mSolo = new Ball();
private JFrame frame;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
ClientClass window = new ClientClass();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the application.
*/
public ClientClass()
{
initialize();
}
/**
* Initialize the contents of the frame.
*/
Logic Logical;
Graphics g;
private void initialize() {
frame = new JFrame();
frame.setBounds(100, 100, 590, 520);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().setLayout(null);
SpriteField panel = new SpriteField();
panel.addMouseListener(new MouseAdapter()
{
public void mouseClicked(MouseEvent e)
{
/* int tX = e.getX();
Logical.MoveBallX();
int tY = e.getY();
Logical.MoveBallY();
panel.repaint();*/
Logical.MoveBallX();
Logical.MoveBallY();
panel.repaint();
}
});
panel.setForeground(Color.WHITE);
panel.setBackground(Color.GRAY);
panel.setBounds(64, 92, 434, 355);
frame.getContentPane().add(panel);
JButton btnStart = new JButton("Start");
btnStart.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e)
{
Graphics2D g2 = (Graphics2D)g;
mSolo.DrawSprite(g2 , Logical.MoveBallX(), Logical.MoveBallY());
}
});
btnStart.setBounds(64, 13, 174, 60);
frame.getContentPane().add(btnStart);
}
}
And here are my other Classes (Logic)
package logicPack;
import clientPackage.Ball;
public class Logic
{
Ball mSolo;
public int MoveBallX()
{
int NewX = mSolo.xPos + 50;
return NewX;
}
public int MoveBallY()
{
int NewY = mSolo.yPos + 50;
return NewY;
}
//Motion, force, friction and collision GO HERE ONLY
}
SpriteField
package clientPackage;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JPanel;
public class SpriteField extends JPanel
{
Ball mSolo;
SpriteField()
{
mSolo = new Ball();
repaint();
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D)g;
mSolo.DrawSprite(g2 , mSolo.xPos , mSolo.yPos);
}
}
Ball
package clientPackage;
import java.awt.Color;
import java.awt.Graphics2D;
public class Ball
{
Ball()
{
}
public int xPos = 25;
public int yPos = 25;
int diameter = 25;
public void DrawSprite(Graphics2D g2, int xPos, int yPos)
{
g2.setColor(Color.BLACK);
g2.fillOval(xPos - diameter / 2 , yPos - diameter / 2 , diameter , diameter);
}
}
If you do not understand my Java comments, you can just ignore them.
If you need more details to help me, let me know.
EDIT 1:
Andrew, the closest article I could find used arrow keys to move a sprite. The article was "Sprite not moving in JPanel". All the other articles I found either addressed JPanels without sprites, or animating a sprite. However, I need a JButton that is MouseClicked to simply start the movement, and the ball does not change shape or color. I believe I have the collision part working, but I'm unable to test it until the ball starts moving.
EDIT 2:
LuxxMiner, Thanks for the hints. I have refined my collision portion to be a little more accurate using the getHeight and getWidth methods.
EDIT 3:
MadProgrammer, Thanks...? The problem is not the painting of the ball, I cannot get the ball to move in the first place to repaint it. And the example uses arrow keys, not a mouse click or JButton.
First, take a look at Painting in AWT and Swing and Performing Custom Painting to understand how painting works in Swing.
Let's have a look at the code...
You have a Ball class, which has it's own properties, but then your DrawSprite method passes in values which override these properties?
public class Ball {
Ball() {
}
public int xPos = 25;
public int yPos = 25;
int diameter = 25;
public void DrawSprite(Graphics2D g2, int xPos, int yPos) {
g2.setColor(Color.BLACK);
g2.fillOval(xPos - diameter / 2, yPos - diameter / 2, diameter, diameter);
}
}
What's the point of that? The Ball should paint it's own current state. You should get rid of the additional parameters
public class Ball {
Ball() {
}
public int xPos = 25;
public int yPos = 25;
int diameter = 25;
public void DrawSprite(Graphics2D g2) {
g2.setColor(Color.BLACK);
g2.fillOval(xPos - diameter / 2, yPos - diameter / 2, diameter, diameter);
}
}
ClientClass, Logic and SpriteField all have their own Ball references, none of which is shared so if Logic where to update the state of it's Ball, neither ClientClass or SpriteField would actually see those changes.
In reality, only SpriteField needs an instance of Ball, as it's basically the "ball container", it has the information need to determine if the ball moves out of bounds and wants to know when the ball should be repainted, better to isolate the functionality/responsibility for the Ball to SpriteField at this time.
You also need a means to actually move the ball. While you could use other events, I'd be nice if the ball just moved itself, to this end, you can use a Swing Timer, which won't block the Event Dispatching Thread, but which notifies the registered ActionListener within the context of the EDT, making it safe to update the UI from within.
public class SpriteField extends JPanel {
private Ball mSolo;
private Timer timer;
private int xDelta, yDelta;
public SpriteField() {
mSolo = new Ball();
do {
xDelta = (int) ((Math.random() * 8) - 4);
} while (xDelta == 0);
do {
yDelta = (int) ((Math.random() * 8) - 4);
} while (yDelta == 0);
}
public void start() {
if (timer == null || !timer.isRunning()) {
timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
mSolo.xPos += xDelta;
mSolo.yPos += yDelta;
if (mSolo.xPos - (mSolo.diameter / 2) < 0) {
mSolo.xPos = mSolo.diameter / 2;
xDelta *= -1;
} else if (mSolo.xPos + (mSolo.diameter / 2) > getWidth()) {
mSolo.xPos = getWidth() - (mSolo.diameter / 2);
xDelta *= -1;
}
if (mSolo.yPos - (mSolo.diameter / 2) < 0) {
mSolo.yPos = (mSolo.diameter / 2);
yDelta *= -1;
} else if (mSolo.yPos + (mSolo.diameter / 2) > getHeight()) {
mSolo.yPos = getHeight() - (mSolo.diameter / 2);
yDelta *= -1;
}
repaint();
}
});
timer.start();
}
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
mSolo.DrawSprite(g2);
}
}
Now, all you need to do, is when the "Start" button is clicked, call the start method
public class ClientClass {
private JFrame frame;
/**
* Launch the application.
*/
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
try {
ClientClass window = new ClientClass();
window.frame.setVisible(true);
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
/**
* Create the application.
*/
public ClientClass() {
initialize();
}
/**
* Initialize the contents of the frame.
*/
// Logic Logical;
private void initialize() {
frame = new JFrame();
frame.setBounds(100, 100, 590, 520);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
SpriteField panel = new SpriteField();
panel.setForeground(Color.WHITE);
panel.setBackground(Color.GRAY);
frame.getContentPane().add(panel);
JButton btnStart = new JButton("Start");
btnStart.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
panel.start();
}
});
frame.getContentPane().add(btnStart, BorderLayout.SOUTH);
}
}
I am attempting to move an star image diagonally across. I am using a Thread to try and achieve this. The program compiles and the image is displayed, however the star won't move at all. I dont think the thread started properly.
Help would be greatly appreciated
drawing class (Board):
//define host package
package star;
//import awt and swing drawing packages
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Toolkit;
//jpanel and other javax classes
import javax.swing.JPanel;
import javax.swing.ImageIcon;
//main board class
public class Board extends JPanel implements Runnable
{
//constructor
Image star; //star image to hold image returned from directory
int x, y; //co ordinates for translation of star image
//delay constant
private final int DELAY = 50;
private Thread animator;
public Board()
{
//set the background colour to black
setBackground(Color.black);
//image directory
ImageIcon ii = new ImageIcon(this.getClass().getResource("star.png"));
//retrieve image from directory
star = ii.getImage();
//paint in memory then screen to improve
setDoubleBuffered(true);
//set star co ords variables
x = y = 10;
}
//initialize thread
void AddNotify()
{
super.addNotify();
//run method in this class
animator = new Thread(this);
animator.start();
}
//jpanel paintComponent() with abstract graphics object
#Override public void paintComponent(Graphics comp)
{
//repaint screen due to animation
super.paintComponent(comp);
Graphics2D comp2d = (Graphics2D) comp;
//draw the star
//class should be notified of drawing
comp2d.drawImage(star, x, y, this);
//sync for linux systems
Toolkit.getDefaultToolkit().sync();
comp.dispose();
}
//set the coordinates for the star image
public void cycle()
{
//move star
x += 1;
y += 1;
//if top corner goes out of range
if (y > 240)
{
x = -45;
y = -45;
}
System.out.println("x: " + x + "y: " + y);
}
//action performed method. Event parameter from the timer
public void run()
{
//beforeTime, timeDiff and sleep variables
//long = 2 x integer
long beforeTime, timeDiff, sleep;
beforeTime = System.currentTimeMillis();
//infinite loop
while (true)
{
//cycle and add notify methods
cycle();
//call the paintComponent method
repaint();
//compute system time
timeDiff = System.currentTimeMillis() - beforeTime;
/*subtracting from delay keeps lag from cycle() & AddNotify()
methods unoticable.
timeDiff will change with each loop cycle
*/
sleep = DELAY - timeDiff;
//compensate for a timeDiff > 50
if (sleep < 0)
{
sleep = 2;
}
//sleep thread in exception
try
{
Thread.sleep(sleep);
}
catch (InterruptedException ie)
{
System.out.println("Thread could not sleep: " + ie.getMessage());
}
//reset beforeTime time
beforeTime = System.currentTimeMillis();
}
}
}
main java frame class:
//import jframe
import javax.swing.JFrame;
//main class
public class Star extends JFrame
{
//constructor
public Star()
{
//title, resize, size, location etc.
add(new Board2());
setTitle("Star animation");
setSize(240, 280);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setLocationRelativeTo(null);
setResizable(false);
setVisible(true);
}
//class instance
public static void main(String[] arguements)
{
new Star();
}
}
Happy to clarify on request.
You're never calling AddNotify method, which is where you start your thread. I'm guessing you have a typo, and you meant addNotify instead (notice the lower case).
When overriding methods, it's useful to add #Override tag, as compiler will complain if the method you're overriding does not exists.
#Override
void addNotify() {
super.addNotify();
//run method in this class
animator = new Thread(this);
animator.start();
}
It seems your Thread start call is not invoked anywhere in your code execution. You are starting the thread in AddNotify method but that method is not called.
Also your code has a compilation problem on this line:
add(new Board2());
there is no class Board2, rather your class name is Board. I expect it is just an error while pasting the code here.