Multiple moving Graphics in Java JFrame - java

I'm new enough to Java and I'm learning Game Design at the minute. I'm kind of at the beginning so its not a Game really.
I'm dealing with a single thread and an array of numbers.I have to get multiple square shapes moving around a screen at the same time. I have the code running fine for one square but run into trouble when I try implement it to multiple squares. I'm working with two Interfaces and I don't know how to use the Array to make multiple cases of a single random square. Any help would really be appreciated.
Cheers guys.
import java.awt.*;
import javax.swing.*;
public class MovingSquares extends JFrame implements Runnable
{
private static final Dimension WindowSize = new Dimension(600, 600);
private static final int NUMGAMEOBJECTS = 30;
private GameObject[] GameObjectsArray = new GameObject[NUMGAMEOBJECTS];
static int strtCoX = (int) (Math.random() * 600);
static int strtCoY = (int) (Math.random() * 600);
public MovingSquares() {
this.setTitle("Crazy squares");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Dimension screensize = java.awt.Toolkit.getDefaultToolkit().getScreenSize();
int x = screensize.width/2 - WindowSize.width/2;
int y = screensize.height/2 - WindowSize.height/2;
setBounds(x, y, WindowSize.width, WindowSize.height);
setVisible(true);
Thread t = new Thread(this);
t.start();
}
public void run() {
while (true) {
try {
Thread.sleep(20);
} catch (InterruptedException e) { }
GameObject.move();
this.repaint();
}
}
public void paint(Graphics g) {
g.setColor(Color.WHITE);
g.fillRect(0, 0, WindowSize.width, WindowSize.height);
int size = 50;
int R = (int) (Math.random() * 256);
int G = (int) (Math.random() * 256);
int B = (int) (Math.random() * 256);
Color c = new Color(R, G, B);
g.setColor(c);
g.fillRect(strtCoX, strtCoY, size, size);
}
public static void main(String[] args) {
MovingSquares M = new MovingSquares();
}
}
Second Interface - GameObject class.
import java.awt.*;
public class GameObject
{
private static double x;
private static double y;
private Color c;
public static int ranCoX = (int) (Math.random() * 20);
public static int ranCoY = (int) (Math.random() * 20);
public GameObject() {
int strtX = (int) x;
int strtY = (int) y;
int speedX = (int) (Math.random() * 10);
int speedY = (int) (Math.random() * 10);
}
public static void move() {
int velX = (int) (Math.random() * ranCoX);
int velY = (int) (Math.random() * ranCoY);
if (MovingSquares.strtCoY > 600)
MovingSquares.strtCoY = MovingSquares.strtCoY - 550;
else if (MovingSquares.strtCoX > 600)
MovingSquares.strtCoX = MovingSquares.strtCoX - 550;
MovingSquares.strtCoX += velX;
MovingSquares.strtCoY += velY;
}
public static void paint(Graphics g) {
int size = 50;
x = (Math.random() * 600);
y = (Math.random() * 600);
int R = (int) (Math.random() * 256);
int G = (int) (Math.random() * 256);
int B = (int) (Math.random() * 256);
Color c = new Color(R, G, B);
g.setColor(c);
g.fillRect((int)x, (int) y, size, size);
}
}

Here's a version of Moving Squares that hopefully will help you understand how to do an animation.
One of the first things I did was to separate the classes into either a model class, a view class, or a controller class. The model / view / controller pattern helps to separate concerns.
Let's look at the revised version of your model class, the GameObject class.
package com.ggl.moving.squares;
import java.awt.Color;
import java.awt.Graphics;
public class GameObject {
private static final int size = 50;
private double x;
private double y;
private double dx;
private double dy;
private int drawingWidth;
public GameObject(int drawingWidth) {
x = Math.random() * drawingWidth;
y = Math.random() * drawingWidth;
dx = Math.random() * 30D - 15D;
dy = Math.random() * 30D - 15D;
this.drawingWidth = drawingWidth;
}
public void move() {
int lowerLimit = size;
int upperLimit = drawingWidth - size;
x += dx;
if (x < lowerLimit) {
x += upperLimit;
} else if (x > upperLimit) {
x -= upperLimit;
}
y += dy;
if (y < lowerLimit) {
y += upperLimit;
} else if (y > upperLimit) {
y -= upperLimit;
}
}
public void draw(Graphics g) {
g.setColor(generateRandomColor());
g.fillRect((int) x, (int) y, size, size);
}
private Color generateRandomColor() {
int R = (int) (Math.random() * 256);
int G = (int) (Math.random() * 256);
int B = (int) (Math.random() * 256);
Color c = new Color(R, G, B);
return c;
}
}
There's only one static field left, the size of the square. Everything else is a dynamic variable or method. This allows us to create more than one instance of the GameObject.
I changed the name of the drawing method from paint to draw. This is so we don't get confused about which methods are Swing methods, and which methods are our methods.
We pass the width of the drawing panel into the constructor. That way, we only have to define the width in one place. You can see in the move method that we allow a margin the size of the square in the drawing area.
The constructor defines a random initial position and initial velocity. Th move method merely keeps the square moving in a straight line.
Next, let's look at the revised version of your main class, the MovingSquares class.
package com.ggl.moving.squares;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import javax.swing.JFrame;
import javax.swing.SwingUtilities;
public class MovingSquares implements Runnable {
private static final int DRAWING_WIDTH = 600;
private static final int NUMGAMEOBJECTS = 30;
private GameObject[] gameObjectsArray = new GameObject[NUMGAMEOBJECTS];
private JFrame frame;
private MovingPanel movingPanel;
private ObjectsRunnable objectsRunnable;
public MovingSquares() {
for (int i = 0; i < gameObjectsArray.length; i++) {
gameObjectsArray[i] = new GameObject(DRAWING_WIDTH);
}
}
#Override
public void run() {
frame = new JFrame();
frame.setTitle("Crazy squares");
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
frame.addWindowListener(new WindowAdapter() {
#Override
public void windowClosing(WindowEvent event) {
exitProcedure();
}
});
movingPanel = new MovingPanel(gameObjectsArray, DRAWING_WIDTH);
frame.add(movingPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
objectsRunnable = new ObjectsRunnable(this, gameObjectsArray);
new Thread(objectsRunnable).start();
}
private void exitProcedure() {
objectsRunnable.setRunning(false);
frame.dispose();
System.exit(0);
}
public void repaintMovingPanel() {
movingPanel.repaint();
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new MovingSquares());
}
}
We define the width of the drawing panel here, as well as an array to hold the game objects.
We start the Swing application on the Event Dispatch thread (EDT) by invoking the SwingUtilities invokeLater method. A Swing application must always start on the EDT.
We create the game objects in the constructor and create the Swing components in the run method. I moved the drawing panel into its own class, which we'll talk about later.
We use a window listener so we can stop the thread when we're done with the application.
We pack the JFrame. The only place we're specifying a size is when we create the drawing panel. We use a JFrame. The only time you extend a Swing component, or any Java class, is when you want to override one of the methods.
Let's look at the drawing panel class, the MovingPanel class.
package com.ggl.moving.squares;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JPanel;
public class MovingPanel extends JPanel {
private static final long serialVersionUID = -6291233936414618049L;
private GameObject[] gameObjectsArray;
public MovingPanel(GameObject[] gameObjectsArray, int width) {
this.gameObjectsArray = gameObjectsArray;
setPreferredSize(new Dimension(width, width));
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.WHITE);
g.fillRect(0, 0, getWidth(), getHeight());
for (int i = 0; i < gameObjectsArray.length; i++) {
gameObjectsArray[i].draw(g);
}
}
}
We override the paintComponent method to draw on the JPanel. Since the game objects draw themselves, we call the draw method on each game object.
Finally, let's look at the animation runnable, the ObjectsRunnable class.
package com.ggl.moving.squares;
import javax.swing.SwingUtilities;
public class ObjectsRunnable implements Runnable {
private volatile boolean running;
private GameObject[] gameObjectsArray;
private MovingSquares movingSquares;
public ObjectsRunnable(MovingSquares movingSquares,
GameObject[] gameObjectsArray) {
this.movingSquares = movingSquares;
this.gameObjectsArray = gameObjectsArray;
this.running = true;
}
#Override
public void run() {
while (running) {
updateObjects();
xxx();
sleep();
}
}
private void updateObjects() {
for (int i = 0; i < gameObjectsArray.length; i++) {
gameObjectsArray[i].move();
}
}
private void xxx() {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
movingSquares.repaintMovingPanel();
}
});
}
private void sleep() {
try {
Thread.sleep(200L);
} catch (InterruptedException e) {
}
}
public synchronized void setRunning(boolean running) {
this.running = running;
}
}
This is a straightforward animation or game loop. I put the repaint call in another SwingUtilities invokeLater method to ensure that the Swing components are updated on the EDT.
I hope this was helpful to you.

Static variables will only be available once per Runtime. To create several GameObjects you have to avoid the static keywords.
Then call new GameObject() several times to create serveral instances of GameObject, each with its own set of variables.
edit:
move the variables strtCoX and strtCoY into GameObject (as suggested by #andrew-thomson )
fix all references to strtCoX and strtCoY inside of GameObject
change g.fillRect(strtCoX, strtCoY, size, size); to for (GameObject currentObject : GameObjectsArray) g.fillRect(currentObject.strtCoX, currentObject.strtCoY, size, size);
Explanation: The coordinate are attributes of GameObject. Each GameObject should have its own coordinates.
=> Your code should work just as before
remove all static keywords in GameObject
change GameObject.move(); to for (GameObject currentObject : GameObjectsArray) currentObject.move();
initialize you GameObjectArray in public MovingSquares() constructor like this: for (int i = 0; i < GameObjectsArray.length; i++) GameObjectsArray[i] = new GameObject();
Explanation: with new GameObject() we are creating 30 instances (in that loop inside the constructor). Therefore we also have to call move() 30 times (and that is why that is also nested in a loop)
=> Wear sunglasses!

Related

Drawing on a JPanel from multiple places

I'm currently working on a 2D game in Java for school. We have to use an Abstract Factory design pattern. For the 2D implementation I use a factory as follows:
public class Java2DFact extends AbstractFactory {
public Display display;
private Graphics g;
public Java2DFact() {
display = new Display(2000, 1200);
}
#Override
public PlayerShip getPlayership()
{
return new Java2DPlayership(display.panel);
}
In my display class I create a JFrame and Jpanel
public class Display {
public JFrame frame;
public JPanel panel;
public int width, height;
public Display(int width, int height) {
this.width = width;
this.height = height;
frame = new JFrame();
frame.setTitle("SpaceInvaders");
frame.setSize(1200,800);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setResizable(false);
frame.setLocationRelativeTo(null);
frame.setVisible(true);
panel = new JPanel(){
#Override
protected void paintComponent(Graphics g){
super.paintComponent(g);
}
};
panel.setFocusable(true);
frame.add(panel);
}
}
Now from my main gameloop I call the visualize method inside the Java2DPLayership class to visualize my Playership
public class Java2DPlayership extends PlayerShip {
private JPanel panel;
private Graphics2D g2d;
private Image image;
private BufferStrategy bs;
public Java2DPlayership(JPanel panel) {
super();
this.panel = panel;
}
public void visualize() {
try {
image = ImageIO.read(new File("src/Bee.gif"));
Graphics2D g = (Graphics2D) bs.getDrawGraphics();
//g.setColor(new Color(0, 0, 0));
//g.fillRect(10, 10, 12, 8);
g.drawImage(image, (int) super.getMovementComponent().x, (int) super.getMovementComponent().y, null);
Toolkit.getDefaultToolkit().sync();
g.dispose();
panel.repaint();
} catch(Exception e){
System.out.println(e.toString());
}
}
}
My goal is to pass around the JPanel to every entity and let it draw its contents onto the panel before showing it. However I can't seem to figure out how to do this. When using this approach by changing the Graphics of the panel I get a lot of flickering.
Here is a fully functional, albeit simple example, I wrote some time ago. It just has a bunch of balls bouncing off the sides of the panel. Notice that the render method of the Ball class accepts the graphics context from paintComponent. If I had more classes that needed to be rendered, I could have created a Renderable interface and have each class implement it. Then I could have a list of Renderable objects and just go thru them and call the method. But as I also said, that would need to happen quickly to avoid tying up the EDT.
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Bounce extends JPanel {
private static final int COLOR_BOUND = 256;
private final static double INC = 1;
private final static int DIAMETER = 40;
private final static int NBALLS = 20;
private final static int DELAY = 5;
private final static int PANEL_WIDTH = 800;
private final static int PANEL_HEIGHT = 600;
private final static int LEFT_EDGE = 0;
private final static int TOP_EDGE = 0;
private JFrame frame;
private double rightEdge;
private double bottomEdge;
private List<Ball> balls = new ArrayList<>();
private Random rand = new Random();
private List<Long> times = new ArrayList<>();
private int width;
private int height;
public Bounce(int width, int height) {
this.width = width;
this.height = height;
frame = new JFrame("Bounce");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(this);
addComponentListener(new MyComponentListener());
frame.pack();
frame.setLocationRelativeTo(null);
rightEdge = width - DIAMETER;
bottomEdge = height - DIAMETER;
for (int j = 0; j < NBALLS; j++) {
int r = rand.nextInt(COLOR_BOUND);
int g = rand.nextInt(COLOR_BOUND);
int b = rand.nextInt(COLOR_BOUND);
Ball bb = new Ball(new Color(r, g, b), DIAMETER);
balls.add(bb);
}
frame.setVisible(true);
}
public Dimension getPreferredSize() {
return new Dimension(width, height);
}
public static void main(String[] args) {
new Bounce(PANEL_WIDTH, PANEL_HEIGHT).start();
}
public void start() {
/**
* Note: Using sleep gives a better response time than
* either the Swing timer or the utility timer. For a DELAY
* of 5 msecs between updates, the sleep "wakes up" every 5
* to 6 msecs while the other two options are about every
* 15 to 16 msecs. Not certain why this is happening though
* since the other timers are run on threads.
*
*/
Timer timer = new Timer(0,(ae)-> {repaint();
for (Ball b : balls) {
b.updateDirection();
}} );
timer.setDelay(5); // 5 ms.
timer.start();
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
for (Ball ball : balls) {
ball.render(g2d);
}
}
class MyComponentListener extends ComponentAdapter {
public void componentResized(ComponentEvent ce) {
Component comp = ce.getComponent();
rightEdge = comp.getWidth() - DIAMETER;
bottomEdge = comp.getHeight() - DIAMETER;
for (Ball b : balls) {
b.init();
}
}
}
class Ball {
private Color color;
public double x;
private double y;
private double yy;
private int ydir = 1;
private int xdir = 1;
private double slope;
private int diameter;
public Ball(Color color, int diameter) {
this.color = color;
this.diameter = diameter;
init();
}
public void init() {
// Local constants not uses outside of method
// Provides default slope and direction for ball
slope = Math.random() * .25 + .50;
x = (int) (rightEdge * Math.random());
yy = (int) (bottomEdge * Math.random()) + diameter;
xdir = Math.random() > .5 ? -1
: 1;
ydir = Math.random() > .5 ? -1
: 1;
y = yy;
}
public void render(Graphics2D g2d) {
g2d.setColor(color);
g2d.fillOval((int) x, (int) y, diameter, diameter);
}
public void updateDirection() {
x += (xdir * INC);
yy += (ydir * INC);
y = yy * slope;
if (x < LEFT_EDGE || x > rightEdge) {
xdir = -xdir;
}
if (y < TOP_EDGE || y > bottomEdge) {
ydir = -ydir;
}
}
}
}

Why does the JFrame glitch when multiple objects are painted?

I am making an asteroid game. Every so often an asteroid needs to be generated and fly across the screen. For some reason when more then 1 asteroid is created, the screen glitches out. If you maximize the screen you will be able to see the glitching. I have tried using paint instead of paintComponent. I have also tried extending JFrame instead of JPanel but that just makes it worse. The class below sets up the screen and handles the game loop
public class Game extends JPanel {
static ArrayList<Asteroids> rocks = new ArrayList<Asteroids>();
//This variable determines whether the game should keep running
static boolean running = true;
//Counter to access arraylist
static int counter = 0;
public static void main(String[] args) throws InterruptedException {
//Creating the window
JFrame frame = new JFrame("Asteroid Game");
frame.getContentPane().setBackground(Color.BLACK);
frame.setSize(1100, 1000);
frame.setLocationRelativeTo(null);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
Asteroids a = new Asteroids();
frame.add(a);
//Game loop
while(running) {
if(counter % 4 == 0) {
rocks.add(new Asteroids());
frame.add(rocks.get(rocks.size() - 1));
}
for(int i = 0; i < rocks.size(); i++) {
rocks.get(i).repaint();
rocks.get(i).move();
if(!rocks.get(i).isPosFine()) {
rocks.remove(i);
i--;
}
}
Thread.sleep(17);
counter++;
}
}
}
The class below sets up the asteroids
public class Asteroids extends JPanel {
//These arrays store the coordinates of the asteroid
private int[] xPos = new int[8];
private int[] yPos = new int[8];
//Determines whether asteroid should be generated from top or bottom
private int[] yGen = {-100, 1100};
//Determines the direction the asteroid shold go
int genLevel;
/**
* #param g Graphics
* This method paints the asteroid
*/
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D r = (Graphics2D)g;
r.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
r.setColor(Color.decode("#52575D"));
r.fillPolygon(xPos, yPos, 8);
}
/**
* This constructor sets up the asteroid location points
*/
public Asteroids() {
int x = (int)(Math.random() * (700 - 1) + 100);
int y = yGen[(int)(Math.random() * (1 + 1 - 0))];
updateAsteroids(x, y);
genLevel = y;
System.out.println("Created!");
}
/**
* #param x int
* #param y int
* This method generates the asteroid based on the points passed in
*/
public void updateAsteroids(int x, int y) {
xPos[0] = x;
xPos[1] = x + 20;
xPos[2] = x + 40;
xPos[3] = x + 35;
xPos[4] = x + 40;
xPos[5] = x + 4;
xPos[6] = x - 16;
xPos[7] = x - 20;
yPos[0] = y;
yPos[1] = y + 7;
yPos[2] = y + 20;
yPos[3] = y + 40;
yPos[4] = y + 80;
yPos[5] = y + 70;
yPos[6] = y + 40;
yPos[7] = y;
}
/**
* This moves the asteroid
*/
public void move() {
int moveSpeedx = (int)(Math.random() * (10 - 1) + 1);
int moveSpeedy = (int)(Math.random() * (10 - 1) + 1);
for(int i = 0; i < 8; i++) {
if(genLevel > 0) {
xPos[i] -= moveSpeedx;
yPos[i] -= moveSpeedy;
}
else {
xPos[i] += moveSpeedx;
yPos[i] += moveSpeedy;
}
}
}
/**
* #return if the asteroid should be kept on the screen or not
*/
public boolean isPosFine() {
for(int i = 0; i < 8; i++) {
if(xPos[i] > 1250 || xPos[i] < -150)
return false;
if(yPos[i] > 1250 || yPos[i] < - 150)
return false;
}
return true;
}
}```
Your biggest problem that I can see is that you made your Asteroids class extend JPanel, making it much heavier weight than it should be, and making it difficult for more than one to show and for them to interact well and easily.
I recommend that you:
Make Asteroid a non-component logical class,
one that knows how to draw itself by giving it a public void draw(Graphics2D g2) method
one that knows how to move itself in response to your game loop's tick
Create one JPanel just for drawing the entire animation
Give this JPanel a collection of Asteroid objects, say in an ArrayList
In this JPanel's paintComponent, loop through all Asteroids in the collection, calling each one's draw(...) method
Drive the whole animation in a Swing thread-safe and controllable way using a Swing Timer. In this timer's actionPerformed, tell each asteroid to move, and then call repaint() on the drawing JPanel
Don't call .repaint() within the loop, but rather after the loop is finished
Create a small BufferedImage sprite from your Shapes, and draw those as the asteroid
A simple example illustrating what I mean:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Polygon;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
#SuppressWarnings("serial")
public class Game2 extends JPanel {
private static final int PREF_W = 1000;
private static final int PREF_H = 800;
private static final int TIMER_DELAY = 20;
private List<Asteroid2> asteroids = new ArrayList<>();
public Game2() {
setBackground(Color.BLACK);
int rows = 5;
int cols = 5;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
Asteroid2 asteroid = new Asteroid2();
asteroid.setX(j * (PREF_W / cols));
asteroid.setY(i * (PREF_H / rows));
asteroids.add(asteroid);
}
}
new Timer(TIMER_DELAY, e -> {
for (Asteroid2 asteroid2 : asteroids) {
asteroid2.move();
}
repaint();
}).start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Asteroid2 asteroid : asteroids) {
asteroid.draw(g);
}
}
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
private static void createAndShowGui() {
Game2 mainPanel = new Game2();
JFrame frame = new JFrame("Game2");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
class Asteroid2 {
private static final int[] POLY_X = { 20, 40, 60, 55, 60, 24, 4, 0 };
private static final int[] POLY_Y = { 0, 7, 20, 40, 80, 70, 40, 0 };
private static final Color ASTEROID_COLOR = Color.decode("#52575D");
private Image image;
private int x;
private int y;
public Asteroid2() {
Polygon poly = new Polygon(POLY_X, POLY_Y, POLY_X.length);
image = new BufferedImage(60, 80, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = (Graphics2D) image.getGraphics();
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2.setColor(ASTEROID_COLOR);
g2.fill(poly);
g2.dispose();
}
public void move() {
x++;
y++;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
public void draw(Graphics g) {
if (image != null) {
g.drawImage(image, x - 20, y, null);
}
}
}

How to multithread with paintComponent()?

I created an app that contains a square that bounces every time it touches an edge of the frame.I don't have issues lunching the app,the problem is that i don't know how to create various threads in order to have multiples squares inside the frame.
I tried multiple things but i can't figure out where i should create the threads.
I also noticed that the square is visible only when i add it directly inside the frame and not when i put it inside a JPanel.
Square.java
public class Square extends JComponent implements ActionListener {
int width = 20;
int height = 20;
double y = Math.random() * 360;
double x = Math.random() * 360;
boolean xMax = false;
boolean yMax = false;
boolean xMin = true;
boolean yMin = true;
Rectangle2D.Double square = new Rectangle2D.Double(x, y, width, height);
public Square() {
Timer t = new Timer(2, this);
t.start();
}
public void paintComponent(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
super.paintComponent(g);
g2.setColor(Color.BLUE);
g2.fill(square);
x_y_rules();
}
public void x_y_rules() {
if (xMax == true) {
x = x - 0.5;
if (x <= 0) {
xMax = false;
}
} else {
x = x + 0.5;
if (x >= this.getWidth()) {
xMax = true;
}
}
if (yMax == true) {
y = y - 0.5;
if (y <= 0) {
yMax = false;
}
} else {
y = y + 0.5;
if (y >= this.getHeight()) {
yMax = true;
}
}
square.setFrame(x, y, width, height);
}
#Override
public void actionPerformed(ActionEvent arg0) {
repaint();
}
}
App.java
public class App extends JFrame {
public static void main(String[] args) {
JFrame jf = new JFrame();
Square sqr = new Square();
jf.setSize(400, 400);
jf.setVisible(true);
jf.add(sqr);
jf.setDefaultCloseOperation(EXIT_ON_CLOSE);
jf.setLocationRelativeTo(null);
}
}
Is it normal that despite i put a time of 2 inside the Timer,the square moves very slowly?
Issues:
you've got program logic, the x_y_rules() method call, inside of the paintComponent method. Get it out as it does not belong there, and instead put it into the Timer's ActionListener code where it belongs.
you can give each Square its own Swing Timer if you want. This isn't really a threading issue since each Timer's ActionListener will run on the EDT.
Two milliseconds is an unrealistic time slice to expect to use in a Swing Timer and no timer will run that fast. 11 to 13 is about the fastest to expect or hope for.
if you want your sprite to move faster, give it a greater value for delta-x and delta-y in your movement code.
Your JComponent has no preferred size defined which is likely why it's not showing up in the JPanel, since the default FlowLayout will size it then to [0, 0]. Override its getPreferredSize() and have it return a reasonable Dimension value.
you're calling setVisible(true) on your JFrame before adding all components, a no-no.
Ok,i put a getPrefferedSize() inside the square class but i've encountered a problem: the squares are not "together",it's like they're bouncing on separate panels
Then your program structure is broken. You really don't want create separate Swing components, and in fact your Square class shouldn't extend JComponent or JPanel. Rather
Square should be a logical class, one that extends from nothing (other than default Object).
Give it a drawing method, say public void draw(Graphics g) {....}
Create one class that extends JPanel, say called DrawingPanel, and override its paintComponent method.
Give the DrawingPanel class an ArrayList<Square> so that it can hold multiple Square objects.
Give the DrawingPanel class a Swing Timer
In the DrawingPanel class's Timer, have it update the position of all the Squares in the ArrayList, and then call repaint()
In the paintComponent method, iterate through all the Squares in the list, using a for loop, and call each one's draw method.
For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.List;
import javax.swing.*;
#SuppressWarnings("serial")
public class DrawingPanel extends JPanel {
private static final int PREF_W = 600;
private static final int PREF_H = PREF_W;
private static final int TIMER_DELAY = 20;
private static final Color[] SQUARE_COLOR = { Color.BLUE, Color.CYAN, Color.DARK_GRAY,
Color.BLACK, Color.GRAY, Color.GREEN, Color.LIGHT_GRAY, Color.MAGENTA, Color.ORANGE,
Color.PINK, Color.RED, Color.YELLOW };
List<Square> squareList = new ArrayList<>();
public DrawingPanel() {
// create a bunch of squares
for (int i = 0; i < SQUARE_COLOR.length; i++) {
squareList.add(new Square(SQUARE_COLOR[i], PREF_W, PREF_H));
}
setBackground(Color.WHITE);
// create and start the timer
new Timer(TIMER_DELAY, new TimerListener()).start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
// simply draw all the squares in the list
for (Square square : squareList) {
square.draw(g);
}
}
// set size of JPanel
#Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
private class TimerListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
// simply iterate through list and move all squares
for (Square square : squareList) {
square.move();
}
repaint(); // then repaint the GUI
}
}
private static void createAndShowGui() {
DrawingPanel mainPanel = new DrawingPanel();
JFrame frame = new JFrame("Drawing Panel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
// this class does *not* extend JPanel or JComponent
class Square {
public static final int WIDTH = 20;
// location of Square
private double sqrX;
private double sqrY;
// X and Y speed
private double deltaX;
private double deltaY;
// width and height of DrawingPanel JPanel
private int dpWidth;
private int dpHeight;
// image to draw
private Image image;
public Square(Color color, int dpWidth, int dpHeight) {
this.dpWidth = dpWidth;
this.dpHeight = dpHeight;
// create square at random location with random speed
sqrX = Math.random() * (dpWidth - WIDTH);
sqrY = Math.random() * (dpHeight - WIDTH);
deltaX = Math.random() * 10 - 5;
deltaY = Math.random() * 10 - 5;
// one way to draw it is to create an image and draw it
image = new BufferedImage(WIDTH, WIDTH, BufferedImage.TYPE_INT_ARGB);
Graphics g = image.getGraphics();
g.setColor(color);
g.fillRect(0, 0, WIDTH, WIDTH);
g.dispose();
}
public void move() {
// check that we're not hitting boundaries
if (sqrX + deltaX < 0) {
deltaX = Math.abs(deltaX);
}
if (sqrX + deltaX + WIDTH >= dpWidth) {
deltaX = -Math.abs(deltaX);
}
sqrX += deltaX;
// check that we're not hitting boundaries
if (sqrY + deltaY < 0) {
deltaY = Math.abs(deltaY);
}
if (sqrY + deltaY + WIDTH >= dpHeight) {
deltaY = -Math.abs(deltaY);
}
sqrY += deltaY;
}
public void draw(Graphics g) {
int x = (int) sqrX;
int y = (int) sqrY;
g.drawImage(image, x, y, null);
}
}

Drawing functions, JFrame wrong results

I have a Problem, I tried to build up a code to visualise Functions.
package main;
import java.awt.*;
import java.awt.Graphics;
import javax.swing.JPanel;
public class MyPanel extends JPanel{
private static final long serialVersionUID = -6649271997955423098L;
#Override
public void paintComponent(Graphics g)
{
//super.paintComponents(g); only show code how it works
//g.setColor(Color.cyan);
//g.fillRect(10, 20, 35, 78);
paintStuff(g);
paintBackgroundComponents(g); // the navigation cross in the background
paintFunctions(g); //Both
leftPaintLineFunction(g); //with test Functions
rightPaintLineFunction(g);
}
/**
* Own Method for drawing lines. Needs Graphics g!
* #param g
*/
private void paintStuff(Graphics g)
{
g.setColor(Color.black);
g.drawString("Integral Veranschaulichung", (Main.length)-(Main.length/3),((Main.width) - (Main.width/12)));
}
private void paintBackgroundComponents(Graphics g)
{
g.drawLine((Main.length)/2, Main.width, (Main.length)/2, 0);
g.drawLine(0, (Main.width)/2, Main.length, (Main.width)/2);
g.setColor(Color.red);
g.drawString("X", (Main.length-(Main.length/8)) , ((Main.width/2) - (Main.width/80)));
g.drawString("Y", ((Main.length/2)+(Main.length/80)) , ((Main.width) - (Main.width/8)));
}
private void paintFunctions(Graphics g)
{
g.setColor(Color.blue);
for(int x=(0 - Main.length); x < Main.length; x++) //fills all possible values (limited through length)
{
g.drawOval((x) + (Main.length/2-1), Functions.solveTestFunction(x)+(Main.width/2-1), 3, 3);
}
}
//needs different methods for left and right Side
private void leftPaintLineFunction(Graphics g)
{
int [] pointOneX = new int [Main.length*2];
int [] pointOneY = new int [Main.length*2];
for(int x = 0; x < Main.length; x++)
{
pointOneX[x] = (((x)*(-1)) + (Main.length/2-1));
pointOneY[x]= (Functions.solveTestFunction(x) + (Main.width/2-1));
System.out.print(pointOneX[x]+" ");
System.out.print(pointOneY[x]);
}
g.drawPolyline(pointOneX, pointOneY, 100);
}
private void rightPaintLineFunction(Graphics g)
{
int [] pointOneX = new int [Main.length*2];
int [] pointOneY = new int [Main.length*2];
for(int x = 0; x < Main.length; x++)
{
pointOneX[x] = ((x) + (Main.length/2+2)); //no clue why 2?
pointOneY[x]= (Functions.solveTestFunction(x) + (Main.width/2));
System.out.print(pointOneX[x]+" ");
System.out.print(pointOneY[x]);
}
g.drawPolyline(pointOneX, pointOneY, 100);
}
}
My other class is:
package main;
public class Functions
{
int x = 0;
public static int solveTestFunction(int x) //simpel test function
{
int y=0;
y = (x^2)*(-1);//-1 cause the gui uses changed sides
return y;
}
}
And my main method is this one:
package main;
import javax.swing.JFrame;
public class Main {
static int length = 1000; // Playgrounds measures
static int width = 1000;
public static void main(String[] args)
{
JFrame frame = new JFrame("My Drawings");
MyPanel panel = new MyPanel();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(length, width); //playground
frame.add(panel);
frame.setVisible(true);
}
}
I split the method rightpaintLineFUnctions() in left and right. Cause that was the only way I get them to work. When I tried x*x for my testFunction everything worked quit good. But since Iwrote it in this form x^2 it didn't worked correct. Some help would be very nice.

animation using repaint()

I am trying to create a simple animation which draws random rectangles when a button is pressed. So far I managed to create rectangle on the press of a button. I want to further develop the code so that when I press the button, more than multiple random rectangles are created. I tried to create a for loop which asks the inner class to repaint itself but it still didn't work. can anyone help me please.
public class TwoButtonsRandomRec {
JFrame frame;
private int width = 500;
private int height = 500;
private DrawPanel dp;
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public static void main (String[] args)
{
TwoButtonsRandomRec test = new TwoButtonsRandomRec();
test.go();
}
public void go()
{
dp = new DrawPanel();
JButton start = new JButton("Start");
start.addActionListener(new startListener());
JButton stop = new JButton("Stop");
stop.addActionListener(new stopListener());
frame = new JFrame();
frame.setSize(getWidth(), getHeight());
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(BorderLayout.NORTH, start);
frame.getContentPane().add(BorderLayout.SOUTH, stop);
}
class startListener implements ActionListener{
public void actionPerformed(ActionEvent event){
frame.getContentPane().add(BorderLayout.CENTER, dp);
frame.repaint();
frame.getRootPane().revalidate();
for(int i=0; i<10; i++){
dp.repaint();
}
}
}
class stopListener implements ActionListener{
public void actionPerformed(ActionEvent event){
System.out.println("stop");
}
}
class DrawPanel extends JPanel{
public void paintComponent(Graphics g){
int w = 5+(int)(Math.random() * width-5);
int h = 5+(int)(Math.random() * height-5);
int maxX = width-w; // diffX & diffY are used to ensure that rectangle is
int maxY = width-h; // draw completely inside the window
int x = (int)(Math.random() * maxX);
int y = (int)(Math.random() * maxY);
Color color = new Color((int) (Math.random()*256), // random red
(int) (Math.random()*256), // random green
(int) (Math.random()*256));// random blue
g.setColor(color);
g.fillRect(x,y,w,h);
}
}
}
repaint() simply tells Swing "when you'll have time, please repaint this area". So if you add rectangles in a loop and call repaint at each iteration, all the rectangles will only appear after the loop has finished, and the action event has been handled.
To have an animation, you need to loop in a separate thread. The easiest way to do that is to use a Swing Timer. When the Start button is started, start a timer which adds a random rectangle and calls repaint() every X milliseconds. When the Stop button is pressed, stop the timer.
What you should do is to put the loop inside paintComponent method and not call repaint in the loop.
So your paintComponent method should look like this:
public void paintComponent(Graphics g){
for (int i = 0; i < 10; i++) {
int w = 5+(int)(Math.random() * width-5);
int h = 5+(int)(Math.random() * height-5);
int maxX = width-w; // diffX & diffY are used to ensure that rectangle is
int maxY = width-h; // draw completely inside the window
int x = (int)(Math.random() * maxX);
int y = (int)(Math.random() * maxY);
Color color = new Color((int) (Math.random()*256), // random red
(int) (Math.random()*256), // random green
(int) (Math.random()*256));// random blue
g.setColor(color);
g.fillRect(x,y,w,h);
}
}
And your action performed should look like this:
public void actionPerformed(ActionEvent event){
frame.getContentPane().add(BorderLayout.CENTER, dp);
frame.repaint();
frame.getRootPane().revalidate();
dp.repaint();
}
Well,here I have done a short EG for you.It displays random rectangles, random times on random screen location.
(You can set your own value of randomization, and the screen location max bound,as per your requirements.)
And also note
int i=(int)(Math.random()*10);
int j=(int)(Math.random()*10);
for(;i<j;i++)
Where at times i may be > than j.So,loop may not work on one or two cliks.Change as per your need.
Here is the working code:
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class SimpleStamper extends JApplet {
public void init() {
Display display = new Display();
setContentPane(display);
}
class Display extends JPanel implements MouseListener {
Display() {
setBackground(Color.black);
addMouseListener(this);
}
public void mousePressed(MouseEvent evt) {
if ( evt.isShiftDown() ) {
repaint();
return;
}
int x = evt.getX();
int y = evt.getY();
Graphics g = getGraphics();
//***MODIFY THE FOLLOWING LINES****//
int i=(int)(Math.random()*10);
int j=(int)(Math.random()*10);
for(;i<j;i++)
{ g.setColor(Color.red);
x=(int)(Math.random()*100);
y=(int)(Math.random()*100);
g.fillRect( x , y , 60, 30 );
g.setColor(Color.black);
g.drawRect(x , y , 60, 30 );}
g.dispose();
}
public void mouseEntered(MouseEvent evt) { }
public void mouseExited(MouseEvent evt) { }
public void mouseClicked(MouseEvent evt) { }
public void mouseReleased(MouseEvent evt) { }
}
}

Categories