I am having some trouble getting both the squares I created in the program, and the ImageIcon imported to show up on my JPanel. I have been at this for a while now, and still am unable to figure out what I can do to make them show up, and allows the player image to move. Here is the first piece of my code which is the Maze class:
public class Maze extends JPanel{
int[][][] mazeArray; //will be defined in the loop depending on how many rectangles are spawned, also for making sure the player can't walk over the walls (first is what number wall it is, second is the x coordiante of the wall, and third is the y coordinate of the wall)
//depending on whether or not the rctangle is vertical or horizontal it will use both values for its height and width (vertical would have sideX for the height, and sideY for the width; this would be the opposite for the horizontal rectangle)
int sideX = 50; //x side length
int sideY = 50; //y side length
int x;
int y;
//setters and getters for use later (for changing the rctangles location and size)
public void setSideX(int sideX){
this.sideX = sideX;
}
public int getSideX(){
return sideX;
}
public void setSideY(int sideY){
this.sideY = sideY;
}
public int getSideY(){
return sideY;
}
public void setCoordinates(int x, int y){
this.x = x;
this.y = y;
}
public void setX(int x){
this.x = x;
}
public int getX(){
return x;
}
public void setY(int y){
this.y = y;
}
public int getY(){
return y;
}
//end setters and getters
public void generateMaze(){
//the left side of the maze
for(int i = 0; i < 10; i++){ //ten blocks on the left side
setX(0); //x is always 0 for the left side
setY(getY() + 50); //adds 50 to the previous Y coordinate amount, m making it go down the whole left side
}
setY(0); //set y back to zero to be able to start the right column of blocks
//the right side of the maze
for(int i = 0; i < 10; i++){
setX(500); //x is always 500 for the right side
setY(getY() + 50); //does the same as it did on the left side, except on the right
}
setY(0); //set y to zero again
setX(50); //start x at 50 since there is no need to remake the corners
for(int i = 0; i < 8; i++){ //only goes up to 8 this this time because the corners of the maze can be ignored
setY(0); //just in case y changes back
setX(getX() + 50); //x increases by 50 each time
}
setY(500);
setX(50);
for(int i = 0; i < 8; i++){ //same as above except for the bottom
setY(500);
setX(getX() + 50);
}
//the maze walls are now generated
}
public void paintComponent(Graphics g){ //for painting the rectangles
super.paintComponent(g);
g.setColor(Color.BLACK);
g.fillRect(getX(), getY(), sideX, sideY); //uses x and y coordinates defined in the generateMaze loop, and the uses whatever current value for the two side, depending on what type of rectangle it is
}
}
This is what I am using to create the mazes walls. Now comes the Player class which deals with the player image, player coordinates, and the player's movement speed:
public class Player {
//Player starts in the top left corner
int playerX = 50;
int playerY = 50;
int moveSpeed = 5; //I can edit move speed here
Image character;
//getters and setters to utilize the player's location and image
public Player(){ //constructor for initial starting points
playerX = 50;
playerY = 50;
ImageIcon player = new ImageIcon("E://Workspace//Maze//images//Player.jpg");
character = player.getImage();
}
public void setPlayerX(int playerX){
this.playerX = playerX;
}
public int getPlayerX(){
return playerX;
}
public void setPlayerY(int playerY){
this.playerY = playerY;
}
public int getPlayerY(){
return playerY;
}
public void setMoveSpeed(int moveSpeed){
this.moveSpeed = moveSpeed;
}
public int getMoveSpeed(){
return moveSpeed;
}
public Image getPlayerImage(){
return character;
}
}
Next is where I think the problem is occurring for the player's image (for the maze I think it is something in the Maze class itself, although it could be a problem in the Layout class as well):
public class Layout extends JPanel implements ActionListener { //GUI with a non null FlowLayout
Maze m = new Maze();
Player p = new Player();
//500 x 500 seemed like a good size for the maze game
int x = 500;
int y = 500;
Image player;
JPanel panel;
public Layout() {
panel = new JPanel();
panel.setLayout(new FlowLayout()); //same as the JFrame
panel.addKeyListener(new Move(p));
panel.setFocusable(true);
panel.setBackground(Color.YELLOW); //background of the maze
m.generateMaze(); //create the maze
}
//for use in setting and getting the borders of the game
public void setX(int x){
this.x = x;
}
public int getX(){
return x;
}
public void setY(int y){
this.y = y;
}
public int getY(){
return y;
}
public JPanel getPanel(){
return panel;
}
#Override //so it can repaint as needed
protected void paintComponent(Graphics g){
super.paintComponent(g);
g.drawImage(p.getPlayerImage(), p.getPlayerX(), p.getPlayerY(), this);
}
public void actionPerformed(ActionEvent ae){
repaint();
}
}
class Move implements KeyListener { //for player movement
final Player p;
Move(Player p){
this.p = p;
}
public void keyPressed(KeyEvent press) { //for the movement in the game
//I used both keys so that if the player woukld like to use WASD or the arrow keys either will work
if(press.getKeyCode() == KeyEvent.VK_W || press.getKeyCode() == KeyEvent.VK_UP){
//move up
p.setPlayerY(p.getPlayerY() - p.getMoveSpeed());
}
else if(press.getKeyCode() == KeyEvent.VK_S || press.getKeyCode() == KeyEvent.VK_DOWN){
//move down
p.setPlayerY(p.getPlayerY() + p.getMoveSpeed());
}
else if(press.getKeyCode() == KeyEvent.VK_A || press.getKeyCode() == KeyEvent.VK_LEFT){
//move left
p.setPlayerX(p.getPlayerX() - p.getMoveSpeed());
}
else if(press.getKeyCode() == KeyEvent.VK_D || press.getKeyCode() == KeyEvent.VK_RIGHT){
//move right
p.setPlayerX(p.getPlayerX() + p.getMoveSpeed());
}
}
public void keyReleased(KeyEvent release) {
//nothing is needed here
}
public void keyTyped(KeyEvent e) {
//does nothing if a key is type (no need for it)
}
}
Lastly is the class that runs it, although I don't think there is any issue here, but just in case I will throw it in here anyway:
public class Play extends JPanel {
public static void main(String[]args) {
play();
}
public static void play() {
JFrame f = new JFrame();
Layout l = new Layout();
JPanel j = l.getPanel();
f.setTitle("Maze Game for final project");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(l.getX(), l.getY()); //size can be changed in layout
f.setVisible(true);
f.add(j); //adds the panel
}
}
I am trying to be able to have the player's image spawn at 50, 50 on start up, and then also have the walls spawn at start up as well. However currently the only thing that is showing up for the JPanel is the yellow background. Help would be greatly appreciated!
New updated code here:
public static void play() {
JFrame f = new JFrame();
Layout l = new Layout();
f.setTitle("Maze Game for final project");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setSize(l.getX(), l.getY()); //size can be changed in layout
f.add(new Layout()); //adds the panel
f.setVisible(true);
}
and also for the Layout constructor
public Layout() {
setLayout(new FlowLayout());
addKeyListener(new Move(p));
setFocusable(true);
setBackground(Color.YELLOW);
m.generateMaze();
}
I can't say that I've gone through all of your code, but one thing did strike me:
Your Layout class extends JPanel and overrides paintComponet, but you never use this class as a JPanel. Instead you use some other JPanel variable within it called panel. Get rid of that variable and instead use the JPanel that is the Layout class itself, and at least some of your problems may be fixed.
e.g.,
public class Layout extends JPanel implements ActionListener {
Maze m = new Maze();
Player p = new Player();
int x = 500;
int y = 500;
Image player;
// JPanel panel;
public Layout() {
// panel = new JPanel();
// panel.setLayout(new FlowLayout()); //same as the JFrame
// panel.addKeyListener(new Move(p));
// panel.setFocusable(true);
// panel.setBackground(Color.YELLOW); //background of the maze
setLayout(new FlowLayout());
addKeyListener(new Move(p));
setFocusable(true);
setBackground(Color.YELLOW);
m.generateMaze();
}
public void setX(int x) {
this.x = x;
}
public int getX() {
return x;
}
public void setY(int y) {
this.y = y;
}
public int getY() {
return y;
}
// public JPanel getPanel() {
// return panel;
// }
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(p.getPlayerImage(), p.getPlayerX(), p.getPlayerY(), this);
}
public void actionPerformed(ActionEvent ae) {
repaint();
}
}
Another problem is your use of KeyListeners since Key Bindings would be much preferable here, but I'll leave that for your next question.
Also, you're adding your JPanel to the JFrame after calling setVisible(true) on the JPanel -- don't do this. Call setVisible(true) only after adding all components.
Related
I'm writing a program that displays a circle every time you click the Jpanel. I have it all set up and I want to be able to use the drawCircle method I created in my circle class to draw the circles in the paintComponent method. I'm storing all of the circles created in a linked list. Then I interate through each Circle in the list and try to use the method in my Circle class called drawCircle().
For some reason, if I try to use c1.drawCircle() in a for loop in the My panel class it only draws the last circle that was created. But if I just use g.fillOval(with the correct parameters grabbing the values from the Circle class) in the for loop it works properly and displays all the circles. Why is it doing this and how do I go about using the method in the Circle class properly
I'm unsure what to try right now.
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.LinkedList;
public class MouseTest {
private int borderWidth = 20;
private JFrame frame;
private boolean tracking;
private boolean start;
private boolean clearBol;
private int xstart;
private int ystart;
private int xend;
private int yend;
private LinkedList<Circle> circles;
public MouseTest() {
tracking = false;
start = false;
circles = new LinkedList<Circle>();
frame = new JFrame();
frame.setBounds(250, 98, 600, 480);
frame.setTitle("Window number three");
Container cp = frame.getContentPane();
JButton clear = new JButton("Clear");
JToggleButton circleButton = new JToggleButton()("Circles");
JToggleButton drawButton = new JToggleButton("Draw");
ButtonGroup circleOrDraw = new ButtonGroup();
MyPanel pane = new MyPanel();
clear.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent ae) {
clearBol = true;
frame.repaint();
}
});
JPanel top = new JPanel();
top.setLayout(new FlowLayout());
top.add(clear);
circleOrDraw.add(circleButton);
circleOrDraw.add(drawButton);
top.add(circleOrDraw);
cp.add(top, BorderLayout.NORTH);
cp.add(pane, BorderLayout.CENTER);
pane.addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
xstart = e.getX();
ystart = e.getY();
start = false;
}
public void mouseReleased(MouseEvent e) {
xend = e.getX();
yend = e.getY();
if (xend < xstart) {
int tmp = xstart;
xstart = xend;
xend = tmp;
}
if (yend < ystart) {
int tmp = ystart;
ystart = yend;
yend = tmp;
}
start = true;
frame.repaint();
}
});
pane.addMouseMotionListener(new MouseMotionAdapter() {
public void mouseMoved(MouseEvent e) {
if (tracking) {
int x = e.getX();
int y = e.getY();
msg("(" + x + ", " + y + ")");
}
}
});
frame.setVisible(true);
} // constructor
public static void main(String[] arg) {
MouseTest first = new MouseTest();
} // main
public void msg(String s) {
System.out.println(s);
}
public void trackMouse() {
tracking = !tracking;
} // trackMouse
public class Circle extends JPanel {
Graphics g;
int x;
int y;
int r;
Color color;
public Circle(Graphics g, int x, int y, int r) {
this.g = g;
this.x = x;
this.y = y;
this.r = r;
int red = (int) (256 * Math.random());
int green = (int) (256 * Math.random());
int blue = (int) (256 * Math.random());
this.color = new Color(red, green, blue);
}
public void drawCircle() {
int x2 = x - (r / 2);
int y2 = y - (this.r / 2);
g.setColor(color);
g.fillOval(x2, y2, this.r, this.r);
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public Color getColor() {
return color;
}
public int getR() {
return r;
}
}
public class MyPanel extends JPanel {
public void paintComponent(Graphics g) {
if (start) {
circles.add(new Circle(g, xend, yend,
(int) ((250 * Math.random() + 4))));
//Area where I'm having issues
for (Circle c1 : circles) {
msg("" + c1.getX());
// this method that I created in the circle class will only draw the first circle
//c1.drawCircle();
int r = c1.getR();
int x = c1.getX();
int y = c1.getY();
g.setColor(c1.getColor());
g.fillOval((c1.getX() - (r / 2)), (c1.getY() - (r / 2)),
r, r); // this will display all the circles
}
int size = circles.size();
msg(size + " Size");
msg("" + circles.getLast().getX());
}
if (clearBol) {
super.paintComponent(g);
circles.clear();
clearBol= false;
}
Thank you!
Most of the structure of your class needs to be changed
Your MyPanel should have a better name to give its functionality, maybe something like DrawingPanel.
The DrawingPanel is then responsible for managing the Circles to be painted. So typically you would just use an ArrayList to hold the Circle information.
Then you would add a method to the class, like addCircle(...) to add the Circle information to the ArrayList and then invoke repaint().
Then in your paintComponent(...) method the first thing you do is invoke super.paintComponent(...) to clear the panel. Then you iterate through the ArrayList and paint all the Circles. There will be no need for the Boolean values to check the state of the class. The ArrayList will either have circles or it won't.
You would also need a method like clearCircles(). This would simply remove all the Circles from the ArrayList and invoke repaint() on itself.
Your Circle class should NOT extend JPanel. It should just be a class that contains the information need to paint the circle: x/y location, size of circle and color of circle.
Now your frame is responsible of displaying your DrawingPanel and the buttons.
When you click the "Clear" button you simply invoke the clearCircles() method of the DrawingPanel.
For your MouseListener you simply invoke the addCircle(...) method of your DrawingPanel once you have all the information needed to create a Circle instance.
For a complete working example that incorporates all these suggestions check out the DrawOnComponent example found in Custom Painting Approaches
I'm using JFrame to display some images. I've just started, and at this point I want to have a character move on screen. I have 2 files, one that extends JFrame (displays the images) and a runner file. One images is a stationary background while the other is the movable character, however, it seems that both images seem to move and the background position being stationary.
I just started using JFrame and have no idea whats going on.
This is the file that extends JPanel:
public class testScene extends JPanel{
private int x = 60;
private int y = 60;
private boolean end = false;
public void paintComponent(Graphics g){
super.paintComponent(g);
ImageIcon background = new ImageIcon("images\\map\\TestMap1.png");
background.paintIcon(this, g, 0, 0);
ImageIcon protag = new ImageIcon("images\\protag\\protag_f.png");
protag.paintIcon(this, g, x, y);
}
public int getX(){return x;}
public int getY(){return y;}
public boolean getEnd(){return end;}
public void setX(int i){x=i;}
public void setY(int i){y=i;}
public void setEnd(boolean b){end = b;}
}
This is the intended Runner:
public class testSceneRunner{
public static void main(String[] args){
testScene scene = new testScene();
scene.setBackground(Color.black);
JFrame jf = new JFrame();
InListner keyIn = new InListner(); //this is a keyboard listner
jf.addKeyListener(keyIn);
jf.setTitle("Test Scene");
jf.setSize(1020,600);
jf.setVisible(true);
jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
jf.add(scene);
while(!scene.getEnd()){
//just changes x or y based on keyboard input
try {
Thread.sleep(25);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(keyIn.getPressed() == 87){
scene.setY(scene.getY()-1);
}
if(keyIn.getPressed() == 65){
scene.setX(scene.getX()-1);
}
if(keyIn.getPressed() == 83){
scene.setY(scene.getY()+1);
}
if(keyIn.getPressed() == 68){
scene.setX(scene.getX()+1);
}
if(keyIn.getPressed() == 69){
scene.setEnd(true);
}
jf.repaint();
}
jf.dispatchEvent(new WindowEvent(jf, WindowEvent.WINDOW_CLOSING));
}
}
I want the background to remain stationary and the character to move when one of the WASD keys is pressed. However, the background moves as well.
I need to create a JPanel, where upon a mouse click a new Sprite must appear in a new thread. This is what I have:
public class BouncingSprites {
private JFrame frame;
private SpritePanel panel = new SpritePanel();
private Sprite ball;
public BouncingSprites() {
frame = new JFrame("Bouncing Sprite");
frame.setSize(400, 400);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(panel);
frame.setVisible(true);
}
public void start(){
panel.animate(); // never returns due to infinite loop in animate method
}
public static void main(String[] args) {
new BouncingSprites().start();
}
}
Class SpritePanel:
public class SpritePanel extends JPanel
{
Sprite sprite;
public SpritePanel()
{
addMouseListener(new Mouse());
}
//method for creating a new ball
private void newSprite (MouseEvent event)
{
sprite = new Sprite(this);
}
public void animate()
{
}
private class Mouse extends MouseAdapter
{
#Override
public void mousePressed( final MouseEvent event )
{
newSprite(event);
}
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
if (sprite != null)
{
sprite.draw(g);
}
}
}
Class Sprite :
public class Sprite implements Runnable
{
public final static Random random = new Random();
final static int SIZE = 10;
final static int MAX_SPEED = 5;
SpritePanel panel;
private int x;
private int y;
private int dx;
private int dy;
private Color color = Color.BLUE;
private Thread animation;
public Sprite (SpritePanel panel)
{
this.panel = panel;
x = random.nextInt(panel.getWidth());
y = random.nextInt(panel.getHeight());
dx = random.nextInt(2*MAX_SPEED) - MAX_SPEED;
dy = random.nextInt(2*MAX_SPEED) - MAX_SPEED;
animation = new Thread(this);
animation.start();
}
public void draw(Graphics g)
{
g.setColor(color);
g.fillOval(x, y, SIZE, SIZE);
}
public void move()
{
// check for bounce and make the ball bounce if necessary
//
if (x < 0 && dx < 0){
//bounce off the left wall
x = 0;
dx = -dx;
}
if (y < 0 && dy < 0){
//bounce off the top wall
y = 0;
dy = -dy;
}
if (x > panel.getWidth() - SIZE && dx > 0){
//bounce off the right wall
x = panel.getWidth() - SIZE;
dx = - dx;
}
if (y > panel.getHeight() - SIZE && dy > 0){
//bounce off the bottom wall
y = panel.getHeight() - SIZE;
dy = -dy;
}
//make the ball move
x += dx;
y += dy;
}
#Override
public void run()
{
while (Thread.currentThread() == animation)
{
move();
panel.repaint();
try
{
Thread.sleep(40);
}
catch ( InterruptedException exception )
{
exception.printStackTrace();
}
}
}
}
This code creates a new moving sprite. However, the previous sprite gets removed from the panel. What I need to do is to add a new sprite with a mouse click, so that the previous sprite is not removed. If I click three times, three sprites should be painted.
How do I change my code to implement that?
Many thanks!
You could replace Sprite sprite; in the SpritePanel class with List<Sprite> sprites = new ArrayList<>();. Then, when you make a new Sprite, add it to the list. Then in your paintComponent method, you could iterate through the list, drawing all of the sprites.
I've been searching for a way to draw a black-and-white array on screen. It's a simple array, just 20x20. What I plan to do is to draw on an array with the mouse so that each pixel "toggles" from black to white and back when clicked, then pass the array as a set of booleans (or integers) to another function. Currently I'm using Swing. I do remember to have used Swing for drawing on a canvas, but I still can't find the actual usage. Should I use a canvas, or instead rely on JToggleButtons?
You can simply use a JFrame (or other Swing component) and override the paint(Graphics) method to draw a representation of the boolean matrix (note that in the case of a lightweight component such as JPanel you should override paintComponent(Graphics). This will give you the click-and-drag capability you require (which is very difficult to achieve using a grid of individual Swing components).
As other people have commented, AWT Canvas doesn't give you anything not provided by Swing components and you'll see in the example below that I've used the createBufferStrategy method also present on JFrame to ensure a non-flicker display.
Note that my example is fairly simple in that it toggles every pixel you drag across rather than the click operation establishing whether you're in "paint" mode or "erase" mode and then exclusively applying black or white pixels for the duration of the drag.
public class Grid extends JFrame {
private static final int SCALE = 10; // 1 boolean value == 10 x 10 pixels.
private static final int SIZE = 20;
private boolean[][] matrix = new boolean[SIZE][SIZE];
private boolean painting;
private int lastX = -1;
private int lastY = -1;
public Grid() throws HeadlessException {
setPreferredSize(new Dimension(SIZE * SCALE, SIZE * SCALE));
setResizable(false);
setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
setBackground(Color.WHITE);
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
painting = true;
tryAdjustValue(e.getPoint());
}
public void mouseReleased(MouseEvent e) {
painting = false;
lastX = -1;
lastY = -1;
}
});
addMouseMotionListener(new MouseMotionListener() {
public void mouseDragged(MouseEvent e) {
tryAdjustValue(e.getPoint());
}
public void mouseMoved(MouseEvent e) {
tryAdjustValue(e.getPoint());
}
});
}
private void tryAdjustValue(Point pt) {
int newX = pt.x / SCALE;
int newY = pt.y / SCALE;
if (painting && isInRange(newX) && isInRange(newY) && (newX != lastX || newY != lastY)) {
// Only invert "pixel" if we're currently in painting mode, both array indices are valid
// and we're not attempting to adjust the same "pixel" as before (important for drag operations).
matrix[newX][newY] = !matrix[newX][newY];
lastX = newX;
lastY = newY;
repaint();
}
}
private boolean isInRange(int val) {
return val >= 0 && val < SIZE;
}
public void paint(Graphics g) {
super.paint(g);
for (int x=0; x<SIZE; ++x) {
for (int y=0; y<SIZE; ++y) {
if (matrix[x][y]) {
g.fillRect(x * SCALE, y * SCALE, SCALE, SCALE);
}
}
}
}
public static void main(String[] args) {
Grid grid = new Grid();
grid.pack();
grid.setLocationRelativeTo(null);
grid.createBufferStrategy(2);
grid.setVisible(true);
}
}
Why not a simple 20 x 20 grid of JPanel held in a GridLayout(20, 20), and flip the panel's background color if clicked via a MouseListener's mousePressed method. You could hold the panels in a 2D array and query their background color whenever the need arises.
You could also use JLabels for this, but you'd have to remember to turn their opaque properties to true. A JButton would work as well or a JToggleButton, ... the options are almost limitless. I do not recommend though that you use AWT (Canvas) as their's no need to step backwards in functionality since Swing handles this so well.
If you get stuck on this, why not come back and show us your code and we'll better be able to give you more specific help.
Another way to solve this is to use a single JPanel and override its paintComponent method. You could give it an int[][] array to serve as its model, and then in the paintComponent method draw rectangles of whatever color desired based on the state of the model. Then give it a MouseListener that changes the state of the model and calls repaint.
e.g.,
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
#SuppressWarnings("serial")
public class BlackWhiteGridPanel extends JPanel {
// can have multiple colors if desired
// public static final Color[] COLORS = {Color.black, Color.red, Color.blue, Color.white};
public static final Color[] COLORS = {Color.black, Color.white};
public static final int SIDE = 20;
private static final int BWG_WIDTH = 400;
private static final int BWG_HEIGHT = BWG_WIDTH;
private int[][] model = new int[SIDE][SIDE]; // filled with 0's.
public BlackWhiteGridPanel() {
addMouseListener(new MouseAdapter() {
#Override
public void mousePressed(MouseEvent e) {
myMousePressed(e);
}
});
}
private void myMousePressed(MouseEvent e) {
// find relative position of mouse press on grid.
int i = (e.getX() * SIDE) / getWidth();
int j = (e.getY() * SIDE) / getHeight();
int value = model[i][j];
// the model can only hold states allowed by the COLORS array.
// So if only two colors, then value can only be 0 or 1.
value = (value + 1) % COLORS.length;
model[i][j] = value;
repaint();
}
public int[][] getModel() {
// return a copy of model so as not to risk corruption from outside classes
int[][] copy = new int[model.length][model[0].length];
for (int i = 0; i < copy.length; i++) {
System.arraycopy(model[i], 0, copy[i], 0, model[i].length);
}
return copy;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
int width = getWidth();
int ht = getHeight();
for (int i = 0; i < model.length; i++) {
for (int j = 0; j < model[i].length; j++) {
Color c = COLORS[model[i][j]];
g.setColor(c);
int x = (i * width) / SIDE;
int y = (j * ht) / SIDE;
int w = ((i + 1) * width) / SIDE - x;
int h = ((j + 1) * ht) / SIDE - y;
g.fillRect(x, y, w, h);
}
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(BWG_WIDTH, BWG_HEIGHT);
}
private static void createAndShowGui() {
BlackWhiteGridPanel mainPanel = new BlackWhiteGridPanel();
JFrame frame = new JFrame("BlackWhiteGrid");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
UPDATE: semicomplex animation + swing timer = trainwreck. The ultimate source of the problems was the java timer, either the swing or utility version. They are unreliable, especially when performance is compared across operating systems. By implementing a run-of-the-mill thread, the program runs very smoothly on all systems. http://zetcode.com/tutorials/javagamestutorial/animation/. Also, adding Toolkit.getDefaultToolkit().sync() into the paintComponent() method noticeably helps.
I wrote some code that animated smoothly in an awt.Applet (but flickered), then I refactored it to java swing. Now it doesn't flicker but it looks choppy. I've messed with the timer but that doesn't work. Any tips or suggestions for smoothly animating swing components would be greatly appreciated.
import java.util.Random;
import java.util.ArrayList;
import java.awt.event.;
import java.awt.;
import javax.swing.*;
//////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////
public class Ball extends JApplet{
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame();
frame.setTitle("And so the ball rolls");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
initContainer(frame);
frame.pack();
frame.setVisible(true);
}
});
}
public static void initContainer(Container container){
GraphicsPanel graphicsPanel = new GraphicsPanel();
MainPanel mainPanel = new MainPanel(graphicsPanel);
container.add(mainPanel);
graphicsPanel.startTimer();
}
#Override
public void init(){
initContainer(this);
}
}
///////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
class MainPanel extends JPanel {
JLabel label = new JLabel("Particles");
GraphicsPanel gPanel;
public MainPanel(GraphicsPanel gPanel){
this.gPanel = gPanel;
add(gPanel);
add(label);
}
}
///////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////
class GraphicsPanel extends JPanel implements MouseListener {
private ArrayList<Particle> ballArr = new ArrayList<Particle>();
private String state="s"; //"s"=spiral, "p"=particle
private int speed=10; //~20 Hz
private Timer timer;
public GraphicsPanel(){
System.out.println("echo from gpanel");
setPreferredSize(new Dimension(500,500));
timer = new Timer(speed, new TimerListener());
addMouseListener(this);
}
public void startTimer(){
timer.start();
}
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
for (Particle b: ballArr){
g.setColor(b.getColor());
g.fillOval(b.getXCoor(),b.getYCoor(),
b.getTheSize(),b.getTheSize());
}
}
public void mousePressed(MouseEvent e) {
ballArr.add(new Particle(e.getX(), e.getY(), state));
}
public void mouseReleased(MouseEvent e) {}
public void mouseEntered(MouseEvent e){}
public void mouseExited(MouseEvent e){}
public void mouseClicked(MouseEvent e) {}
class TimerListener implements ActionListener {
public void actionPerformed(ActionEvent e){
for (Particle b: ballArr)
b.move();
setBackground(Color.WHITE);
repaint();
}
}
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
class Particle
{
private static int instanceCount; {{instanceCount++;}}
private int z = 11, t=1, u=1;
private int[] RGB = new int[3];
private int[] randomizeColor = new int[3];
private double radius, theta;
private int x, y, centerX, centerY, size, spiralDirection=1,
ballSizeLowerBound, ballSizeUpperBound,
radiusLowerBound, radiusUpperBound,
mouseInputX, mouseInputY,
radiusXMultiplier, radiusYMultiplier;
private Color color;
private String state;
private Random random = new Random();
///////////////////////////////////////////////////////////////////////////
public Particle(int x, int y, int centerX, int centerY, int radius,
int theta, int size, Color color){
this.x=x;this.y=y;this.centerX=centerX;this.centerY=centerY;
this.radius=radius;this.theta=theta;this.size=size;this.color=color;
}
public Particle(int mouseInputX, int mouseInputY, String state){
this.mouseInputX=mouseInputX;
this.mouseInputY=mouseInputY;
this.state=state;
//randomize color
RGB[0] = random.nextInt(252);
RGB[1] = random.nextInt(252);
RGB[2] = random.nextInt(252);
randomizeColor[0] = 1+random.nextInt(3);
randomizeColor[0] = 1+random.nextInt(3);
randomizeColor[0] = 1+random.nextInt(3);
centerX=mouseInputX;
centerY=mouseInputY;
if (state.equals("s")){ //setup spiral state
ballSizeLowerBound=5;
ballSizeUpperBound=18;
radiusLowerBound=0;
radiusUpperBound=50;
radiusXMultiplier=1;
radiusYMultiplier=1;
}
if (state.equals("p")){ //setup particle state
ballSizeLowerBound = 15;
ballSizeUpperBound =20 + random.nextInt(15);
radiusLowerBound = 5;
radiusUpperBound = 15+ random.nextInt(34);
radiusXMultiplier=1 + random.nextInt(3);
radiusYMultiplier=1 + random.nextInt(3);
}
size = ballSizeUpperBound-1; //ball size
radius = radiusUpperBound-1;
if (instanceCount %2 == 0) // alternate spiral direction
spiralDirection=-spiralDirection;
}
///////////////////////////////////////////////////////////////////////////
public int getXCoor(){return centerX+x*spiralDirection;}
public int getYCoor(){return centerY+y;}
public int getTheSize(){return size;}
public Color getColor(){return color;}
//////////////////////////////////////////////////////////////////////////
void move(){
//spiral: dr/dt changes at bounds
if (radius > radiusUpperBound || radius < radiusLowerBound)
u = -u;
//spiral shape formula: parametric equation for the
//polar equation radius = theta
x = (int) (radius * radiusXMultiplier * Math.cos(theta));
y = (int) (radius * radiusYMultiplier * Math.sin(theta));
radius += .1*u;
theta += .1;
//ball size formula
if (size == ballSizeUpperBound || size == ballSizeLowerBound)
t = -t;
size += t;
//ball colors change
for (int i = 0; i < RGB.length; i++)
if (RGB[i] >= 250 || RGB[i] <= 4)
randomizeColor[i] = -randomizeColor[i];
RGB[0]+= randomizeColor[0];
RGB[1]+= randomizeColor[1];
RGB[2]+= randomizeColor[2];
color = new Color(RGB[0],RGB[1],RGB[2]);
}
}
Don't set a constant interval timer. Set the timer to go off once -- in the handler
Get the current time (save in frameStartTime)
Do your frame
Set the timer to go off in: interval - (newCurrentTime - frameStartTime)
Should be smoother. If you want to go really pro (and stay in Java), I think you have to consider JavaFX.