1

Ok so I am very new to Java Swing and a beginner in Java in general. My current problem is I have designed a "cityscape". I am working on a UFO flying around, but my randomly generated buildings continue to get regenerated. I am wondering if there is a way to save my instance of buildings to an ArrayList as I have attempted, and paint that selection from that list each time paint is called. I tried what I thought of and I believe it just crashed it when run, because it didn't even open a JFrame and instead produced errors upon errors. Here is what I have:

CityScape class (the main class):

import java.awt.*; 
import javax.swing.*;
public class CityScape extends JPanel 
{     
  Buildings a = new Buildings ();
UFO b = new UFO();

  @Override
  public void paint (Graphics g)
  {
    //RememberBuildings.buildingList.get(1).paint(g);
    a.paint(g);
    b.paint(g);
  }
  public void move()
  {
    b.move();
  }


  public static void main(String[] args) throws InterruptedException
  { 
    JFrame frame = new JFrame("Frame"); 
    CityScape jpe = new CityScape();
    frame.add(jpe);
    frame.setSize(800, 750); 
    frame.setBackground(Color.BLACK);
    frame.setLocationRelativeTo(null);
    frame.setVisible(true);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    System.out.println(frame.getContentPane().getSize());
    while (true)
    {
      jpe.move(); //Updates the coordinates
      jpe.repaint(); //Calls the paint method
      Thread.sleep(10); //Pauses for a moment
    }
  }
}

Buildings class (the class that generates the buildings):

import java.awt.*;

public class Buildings
{

  private int maxX = 784;
  private int maxY = 712;
  private int width = (int)(Math.random()*100+100);
  private int height = (int)(Math.random()*350+100);
  private  int rows = Math.round((height)/25);
  private int columns = Math.round(width/25);

  public void addBuilding()
  {
  RememberBuildings.addBuilding();
  }

  public void paint(Graphics g) 
  { 
    Graphics2D g2d = (Graphics2D) g;

    Color transYellow = new Color (255, 255, 0, 59);

    g2d.setColor(Color.BLACK);
    g2d.fillRect(0, 0, maxX, maxY);

    g2d.setColor(Color.WHITE);
    g2d.fillRect(5, 5, 25, 25);

    int a = 0;

    for (int i =10; i<634; i+=(a+10))//buildings
    {

      g2d.setColor(Color.GRAY);
      g2d.drawRect(i, maxY-height, width, height);
      g2d.fillRect(i, maxY-height, width, height);


      rows = Math.round((height)/25);
      columns = Math.round(width/25);

      for (int j = 1; j<=columns; j++)//windows
      {
        for (int k = 1; k<=rows; k++)
        {
          g2d.setColor(Color.BLACK);
          g2d.drawRect(i+5*j+20*(j-1), (maxY-height)+5*k+20*(k-1), 20, 20);
          if (Math.random()<0.7)
          {
            g2d.setColor(Color.YELLOW);
            g2d.fillRect(i+5*j+20*(j-1), (maxY-height)+5*k+20*(k-1), 20, 20);
          }
          else
          {
            g2d.setColor(Color.BLACK);
            g2d.fillRect(i+5*j+20*(j-1), (maxY-height)+5*k+20*(k-1), 20, 20);
            g2d.setColor(transYellow);
            g2d.fillRect(i+5*j+20*(j-1), (maxY-height)+5*k+20*(k-1), 20, 20);
          }
        }
      }
      addBuilding();
      a = width;
      height = (int)(Math.random()*462+100);
      width = (int)(Math.random()*100+100);

    }
  }
}

RememberBuildings class (the point of this is to add an instance to an ArrayList):

import java.util.*;
public class RememberBuildings
{
  public static ArrayList<Buildings> buildingList = new ArrayList<Buildings>();

  public static void addBuilding()
  {
    buildingList.add(new Buildings());
  }
}

And finally my UFO class (creates the UFO flying by):

import java.awt.*;
import javax.swing.*;
public class UFO extends JPanel
{
  private int x = 20; //x and y coordinates of the ball
  private int y = 20;
  private int xa = 1;
  public void move() //Increase both the x and y coordinates
  {
    if (x + xa < 0) {
      xa = 1;
    }
    if (x + xa > 784-75) 
    {
      xa = -1;
    }
    x = x + xa; 
  }
  public void paint(Graphics g)
  {
    super.paint(g); //Clears the panel, for a fresh start
    Graphics2D g2d = (Graphics2D) g;
    g2d.setColor(Color.LIGHT_GRAY);
    g2d.fillOval(x,y,75,25); //Draw the ball at the desired point
  }
}

Example

Sam Mitchell
  • 194
  • 2
  • 18
  • What kind of errors are you getting – Mahmoud.M Nov 11 '15 at 00:22
  • @Mahmoud.M Well they are not errors that stop it from running, and I think copying and pasting them would cause a lot of confusion. If you copy and run my classes, run from the main class listed above, then you will see it run. But if you uncomment `//RememberBuildings.buildingList.get(1).paint(g);` in the main class (CityScape) you will be able to run it but it crashes – Sam Mitchell Nov 11 '15 at 00:30

3 Answers3

3
  • Avoid overriding paint, use paintComponent instead. Always call the super paint method before you do any custom painting to ensure that the paint chain is maintained. See Painting in AWT and Swing and Performing Custom Painting for more details
  • Beware, Swing is not thread safe and it's unwise to update any component (or any variable that a component may rely on) from outside the context of the Event Dispatching Thread. A simple solution might be to use a Swing Timer instead of a while (true) loop and Thread.sleep. See How to use Swing Timers for more details.
  • You should also only create and modify UI components from within the context of the event dispatching thread, see Initial Threads for more details
  • If you have a problem with your code not working, you should consider providing a runnable example which demonstrates your problem. This is not a code dump, but an example of what you are doing which highlights the problem you are having. This will result in less confusion and better responses. Providing code which is not runnable and is missing classes makes it difficult to know why it's not working and how to fix it.
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • Thank you! Unfortunately the last question you saw, when you suggested `paintComponent`, I could not figure out how to utilize it properly so I stuck with `paint`. I will definitely try the `Timer`! I will also look at that Initial Threads link. This is a runnable example of my issue as it presents the whole problem. The buildings constantly regenerate while the ship moves. That is shown in my code and I am not sure how to fix this. This exact example is runnable and I have commented out my attempt at an ArrayList. – Sam Mitchell Nov 11 '15 at 00:28
  • *"I could not figure out how to utilize it properly so I stuck with paint"* - Then you need to figure out how to use `paintComponent`, as using `paint` is going to cause you no end of issues. The fact that you've broken the paint chain is probably a good start of your problems. "*his is a runnable example of my issue"* which is currently missing `JPanelExample` and `Building`, which makes it uncompliable and therefore, not runnable – MadProgrammer Nov 11 '15 at 00:32
  • I will figure out `paintComponent`. This is a runnable example. I just noticed that `JPanelExample jpe = new JPanelExample();` should be `CityScape jpe = new CityScape();` but that however did not change whether or not it compiles for me. It did and still does compile. Sorry, I'm confused, is there any way you could specify in more detail what is stopping you from compiling? – Sam Mitchell Nov 11 '15 at 00:46
  • `C:\Users\shane\Documents\NetBeansProjects\JavaApplication6\src\javaapplication6\CityScape.java:111: error: cannot find symbol, public static ArrayList buildingList = new ArrayList();, symbol: class Building, location: class RememberBuildings` – MadProgrammer Nov 11 '15 at 00:49
  • Wow my compiler didn't catch this typo, nor did I, thank you. It has been updated. However, if `//RememberBuildings.buildingList.get(1).paint(g);` is uncommented in the CityScape class and `a.paint(g);` is commented, I think it will help you see what I mean. – Sam Mitchell Nov 11 '15 at 00:55
2

A few things here:

  1. To address the paintComponent note and view an example, check out this other thread: Concerns about the function of JPanel: paintcomponent()
  2. There seems to be a bit of a disconnect between the logic you've got going and the object-oriented programming logic that I think will help sort things out (for general info on OOP: https://en.wikipedia.org/wiki/Object-oriented_programming):

What You've Got:

The Structure you've got going is as follows:

  • CityScape :: here's where you've extended JPanel and setup the main function
  • UFO :: an object class that represents 1 UFO
  • Building :: a class that has methods for drawing randomized buildings and calling methods in RememberBuildings
  • RememberBuildings :: I think this is intended to track buildings that have been drawn

The issue here is that your Building class's paint method continually draws multiple newly randomized buildings instead of a set building that retains its structure.

My Suggestion:

There are plenty of solutions to this issue and different ways to implement each solution, but my recommendation is to remodel your Building class in an OOP fashion, meaning that it would represent 1 single building (truer to the name of the class). This would contain a constructor that initializes all of the randomized dimensions of that single building once and draws that single building on the jpanel. Then you would need to keep an array or list of some sort in the cityscape that contains buildings that are part of the cityscape, eliminating the need for a "RememberBuildings" class. so roughly:

CityScape extends JPanel:
    variables:
        Building[] buildings;    //might be useful to use an arraylist/stack/queue instead of an array depending on implementation
        UFO craft;

    constructor:
        setup new Building objects and add to list buildings
        initialize craft to new UFO

    paintComponent:
        calls the paint methods for each building & the ufo craft

Building:
    variables:
        int x, y; // position of building
        int height, width; // of this building

    constructor:
        initializes x, y // probably needs to be inputed from CityScape with this setup
        calc height and width randomly // stored in this.height/width

    paint:
        paints single building based on it's variables

//side-note, you'll probably need getters for the x/y/width to build each building from CityScape

Everything else should be much the same.

Good Luck !

Community
  • 1
  • 1
K.F
  • 459
  • 3
  • 12
1

So, every time Buildings#paint is called, it regenerates all the builds, which is done randomly.

public void paint(Graphics g) {
    Graphics2D g2d = (Graphics2D) g;

    Color transYellow = new Color(255, 255, 0, 59);

    g2d.setColor(Color.BLACK);
    g2d.fillRect(0, 0, maxX, maxY);

    g2d.setColor(Color.WHITE);
    g2d.fillRect(5, 5, 25, 25);

    int a = 0;

    for (int i = 10; i < 634; i += (a + 10))//buildings
    {

        g2d.setColor(Color.GRAY);
        g2d.drawRect(i, maxY - height, width, height);
        g2d.fillRect(i, maxY - height, width, height);

        rows = Math.round((height) / 25);
        columns = Math.round(width / 25);

        for (int j = 1; j <= columns; j++)//windows
        {
            for (int k = 1; k <= rows; k++) {
                g2d.setColor(Color.BLACK);
                g2d.drawRect(i + 5 * j + 20 * (j - 1), (maxY - height) + 5 * k + 20 * (k - 1), 20, 20);
                if (Math.random() < 0.7) {
                    g2d.setColor(Color.YELLOW);
                    g2d.fillRect(i + 5 * j + 20 * (j - 1), (maxY - height) + 5 * k + 20 * (k - 1), 20, 20);
                } else {
                    g2d.setColor(Color.BLACK);
                    g2d.fillRect(i + 5 * j + 20 * (j - 1), (maxY - height) + 5 * k + 20 * (k - 1), 20, 20);
                    g2d.setColor(transYellow);
                    g2d.fillRect(i + 5 * j + 20 * (j - 1), (maxY - height) + 5 * k + 20 * (k - 1), 20, 20);
                }
            }
        }
        addBuilding();
        a = width;
        height = (int) (Math.random() * 462 + 100);
        width = (int) (Math.random() * 100 + 100);

    }
}

There's two ways you might be able to solve this, which you use will depend on what you want to achieve. You could render the buildings directly to a BufferedImage and simply paint that on each paint cycle or you could cache the information you need in order to re-create the buildings.

The BufferedImage approach is quicker, but can't be animated, so if you want to animate the buildings in some way (make the lights flicker), you will need to build up a series of information which allows you to simply repaint them.

I'm going for the second, as you've asked about painting assets from a ArrayList.

I started by translating your "paint" code into a single concept of a virtual building, which has also has information about it's own lights.

public class Building {

    protected static final Color TRANS_YELLOW = new Color(255, 255, 0, 59);

    private int x, y, width, height;
    private List<Light> lights;

    public Building(int x, int y, int width, int height) {
        this.x = x;
        this.y = y;
        this.width = width;
        this.height = height;

        lights = new ArrayList<>(25);
        int rows = Math.round((height) / 25);
        int columns = Math.round(width / 25);

        for (int j = 1; j <= columns; j++)//windows
        {
            for (int k = 1; k <= rows; k++) {
                Color color = null;
                if (Math.random() < 0.7) {
                    color = Color.YELLOW;
                } else {
                    color = TRANS_YELLOW;
                }
                lights.add(new Light(x + 5 * j + 20 * (j - 1), y + 5 * k + 20 * (k - 1), color));
            }
        }
    }

    public void paint(Graphics2D g2d) {
        g2d.setColor(Color.GRAY);
        g2d.drawRect(x, y, width, height);
        g2d.fillRect(x, y, width, height);
        for (Light light : lights) {
            light.paint(g2d);
        }
    }

    public class Light {

        private int x, y;
        private Color color;

        public Light(int x, int y, Color color) {
            this.x = x;
            this.y = y;
            this.color = color;
        }

        public void paint(Graphics2D g2d) {
            g2d.setColor(Color.BLACK);
            g2d.fillRect(x, y, 20, 20);
            g2d.setColor(color);
            g2d.fillRect(x, y, 20, 20);
        }
    }

}

This allows you to generate the primary parameters for the Building and simple cache the results and when needed, simply paint it.

For example...

public class Buildings {

    private int maxX = 784;
    private int maxY = 712;

    private List<Building> buildings;

    public Buildings() {
        buildings = new ArrayList<>(25);
        for (int i = 10; i < 634; i += 10)//buildings
        {
            int width = (int) (Math.random() * 100 + 100);
            int height = (int) (Math.random() * 350 + 100);
            int x = i;
            int y = maxY - height;

            buildings.add(new Building(x, y, width, height));
        }
    }

    public void paint(Graphics g) {
        Graphics2D g2d = (Graphics2D) g;
        for (Building building : buildings) {
            building.paint(g2d);
        }
    }
}

I also changed your UFO class so it no longer extends from JPanel, as it just doesn't need to and is probably the primary cause of confusion with your painting.

I then updated your paint method in your CityScape to use paintComponent instead...

public class CityScape extends JPanel {

    Buildings a = new Buildings();
    UFO b = new UFO();

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        a.paint(g);
        b.paint(g);
    }

As a runnable example...

City Scape

import java.awt.Color;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;

public class CityScape extends JPanel {

    Buildings a = new Buildings();
    UFO b = new UFO();

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g); //To change body of generated methods, choose Tools | Templates.
        a.paint(g);
        b.paint(g);
    }

    public void move() {
        b.move();
    }

    public static void main(String[] args) throws InterruptedException {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                    ex.printStackTrace();
                }

                JFrame frame = new JFrame("Frame");
                CityScape jpe = new CityScape();
                frame.add(jpe);
                frame.setSize(800, 750);
                frame.setBackground(Color.BLACK);
                frame.setLocationRelativeTo(null);
                frame.setVisible(true);
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                System.out.println(frame.getContentPane().getSize());

                Timer timer = new Timer(10, new ActionListener() {
                    @Override
                    public void actionPerformed(ActionEvent e) {
                        jpe.move(); //Updates the coordinates
                        jpe.repaint(); //Calls the paint method
                    }
                });
                timer.start();
            }
        });
    }

    public class Buildings {

        private int maxX = 784;
        private int maxY = 712;

        private List<Building> buildings;

        public Buildings() {
            buildings = new ArrayList<>(25);
            for (int i = 10; i < 634; i += 10)//buildings
            {
                int width = (int) (Math.random() * 100 + 100);
                int height = (int) (Math.random() * 350 + 100);
                int x = i;
                int y = maxY - height;

                buildings.add(new Building(x, y, width, height));
            }
        }

        public void paint(Graphics g) {
            Graphics2D g2d = (Graphics2D) g;
            for (Building building : buildings) {
                building.paint(g2d);
            }
        }
    }

    public static class Building {

        protected static final Color TRANS_YELLOW = new Color(255, 255, 0, 59);

        private int x, y, width, height;
        private List<Light> lights;

        public Building(int x, int y, int width, int height) {
            this.x = x;
            this.y = y;
            this.width = width;
            this.height = height;

            lights = new ArrayList<>(25);
            int rows = Math.round((height) / 25);
            int columns = Math.round(width / 25);

            for (int j = 1; j <= columns; j++)//windows
            {
                for (int k = 1; k <= rows; k++) {
                    Color color = null;
                    if (Math.random() < 0.7) {
                        color = Color.YELLOW;
                    } else {
                        color = TRANS_YELLOW;
                    }
                    lights.add(new Light(x + 5 * j + 20 * (j - 1), y + 5 * k + 20 * (k - 1), color));
                }
            }
        }

        public void paint(Graphics2D g2d) {
            g2d.setColor(Color.GRAY);
            g2d.drawRect(x, y, width, height);
            g2d.fillRect(x, y, width, height);
            for (Light light : lights) {
                light.paint(g2d);
            }
        }

        public class Light {

            private int x, y;
            private Color color;

            public Light(int x, int y, Color color) {
                this.x = x;
                this.y = y;
                this.color = color;
            }

            public void paint(Graphics2D g2d) {
                g2d.setColor(Color.BLACK);
                g2d.fillRect(x, y, 20, 20);
                g2d.setColor(color);
                g2d.fillRect(x, y, 20, 20);
            }
        }

    }

    public class UFO {

        private int x = 20; //x and y coordinates of the ball
        private int y = 20;
        private int xa = 1;

        public void move() //Increase both the x and y coordinates
        {
            if (x + xa < 0) {
                xa = 1;
            }
            if (x + xa > 784 - 75) {
                xa = -1;
            }
            x = x + xa;
        }

        public void paint(Graphics g) {
            Graphics2D g2d = (Graphics2D) g;
            g2d.setColor(Color.LIGHT_GRAY);
            g2d.fillOval(x, y, 75, 25); //Draw the ball at the desired point
        }
    }
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • 1 error found: File: (no associated file) [line: (no source location)] Error: Compile exception: java.lang.IllegalArgumentException: invalid flag: -Xlint:switchcheck – Sam Mitchell Nov 11 '15 at 02:12
  • You've passed an invalid flag to the compiler, nothing to do with the code – MadProgrammer Nov 11 '15 at 02:15
  • I copied exactly what you have though? – Sam Mitchell Nov 11 '15 at 02:16
  • As I said, it has nothing to do with the code but with how you are compiling it. I just copy, pasted, compiled and run the code it works fine. For some reason you are passing `-Xlint:switchcheck` to the compiler, but the flag is invalid – MadProgrammer Nov 11 '15 at 02:28
  • The compiler I use is Dr. Java and I just click compile as usual. Do you have any other compiler suggestions? – Sam Mitchell Nov 11 '15 at 02:35
  • Not really, I just use the plain old Oracle compiler (Java 8). You could dig around the settings of Dr Java and see what compiler flags its trying to use – MadProgrammer Nov 11 '15 at 02:43
  • Oh yeah sorry me too. I meant the IDE is Dr Java if that makes a difference? – Sam Mitchell Nov 11 '15 at 02:44
  • I can't tell if this works because I keep getting that error – Sam Mitchell Nov 11 '15 at 03:11
  • I just copy, pasted, compiled and executed the code in DrJava without issue. Goto Edit|Preferences, select "Compiler Options" and deselect "Show Fall-Through Warnings" – MadProgrammer Nov 11 '15 at 03:22
  • Ok worked after that, however this did not change the regenerating of the buildings... – Sam Mitchell Nov 11 '15 at 03:25
  • Yes it does, make sure you're running the right version of the code - Try starting with a fresh project – MadProgrammer Nov 11 '15 at 03:26
  • Just copied your runnable example to make sure, and as the ship flies the buildings are constantly regenerated – Sam Mitchell Nov 11 '15 at 03:32
  • See update, I've uploaded an animated gif of the code running, it runs fine. Nothing wrong with the code. Even ran perfectly find in DrJava. The problem isn't with the code – MadProgrammer Nov 11 '15 at 03:36
  • Ok figured it out, but why did so many buildings generate and not just like 4-6 kind of like my original? – Sam Mitchell Nov 11 '15 at 03:48
  • I did my best to "guess" at the intention of your code an break it down, this `for (int i = 10; i < 634; i += 10)//buildings` is basically make decisions about the number of builds that should be created, which is basically generating something like 53(?) or so buildings – MadProgrammer Nov 11 '15 at 04:01
  • oh yeah no you totally helped , just wondering. Yeah basically I was creating buildings until the maximum width was reached altogether with the space between buildings and their widths. The max width in this case was 634 – Sam Mitchell Nov 11 '15 at 04:03
  • You could use a `while-loop`, which basically keeps track of the current `x` position which you could "randomly" increase or increase by the last width of building or some such :P – MadProgrammer Nov 11 '15 at 04:06