0

I am making the game Asteroids. Everything functionally works properly, but originally I set the background color to black and had the objects represented by shapes that moved around on a Canvas. I've since changed the background to an Image and am working on changing the objects to be represented by images.

However, regardless of the background, I am having trouble repainting the image in a new location. You can see the path of each object after its been moved to each new location. I've been mostly focusing on the shot fired and I noticed if I fire shots all around the screen, the background is refreshed, but it almost seems to be completely at random. If anyone could help guide me in the right direction, that would be great! I've read several documents, textbooks, and watched several videos to try to understand.

package comets;


import java.awt.*;
import java.awt.event.*;
import java.awt.geom.AffineTransform;
import java.awt.image.AffineTransformOp;
import java.awt.image.BufferedImage;

import javax.imageio.ImageIO;
import javax.sound.sampled.*;
import javax.swing.*;

import java.util.*;
import java.io.*;
import java.net.URL;

// This class is primarily responsible for organizing the game of Comets
public class CometsMain extends JPanel implements KeyListener 
{



    // GUI Data
    private JFrame frame; // The window itself
    private JPanel playArea;  // The area where the game takes place



    private final int playWidth = 500; // The width of the play area (in pixels)
    private final int playHeight = 500; // The height of the play area (in pixels)

    // Game Data
    private SpaceObject spaceObject;
    private Ship ship; // The ship in play
    private Shot s = new Shot(0, 0, 0, 0);
    private LargeComet large = new LargeComet(0, 0, 0, 0);
    private MediumComet medium = new MediumComet(0, 0, 0, 0);
    private SmallComet small = new SmallComet(0, 0, 0, 0);
    private Vector<Shot> shots; // The shots fired by the player
    private Vector<Comet> comets; // The comets floating around

    private boolean shipDead; // Whether or not the ship has been blown up
    private long shipTimeOfDeath; // The time at which the ship blew up

    // Keyboard data
    // Indicates whether the player is currently holding the accelerate, turn
    // left, or turn right buttons, respectively
    private boolean accelerateHeld = false;
    private boolean turnLeftHeld = false;
    private boolean turnRightHeld = false;
    private boolean slowDownHeld = false;

    // Indicates whether the player struck the fire key
    private boolean firing = false;


    // Create Images 
    private Image background; // background image
    private BufferedImage spaceShip = null;
    private BufferedImage largeComet = null;
    private BufferedImage mediumComet = null;
    private BufferedImage smallComet = null;
    private BufferedImage bullet = null;
    private int type = AlphaComposite.SRC_OVER;
    private float alpha = 0;










    // Set up the game and play!
    public CometsMain()
    {





        // Get everything set up
        configureGUI();
        configureGameData();



        // Display the window so play can begin
        frame.setVisible(true);

        //Use double buffering
        frame.createBufferStrategy(2);

        //play music
        playMusic();

        // Start the gameplay
        playGame();






    }

    private void playMusic(){

        try {

            URL url = this.getClass().getClassLoader().getResource("BackgroundMusic.wav");
            AudioInputStream audioIn = AudioSystem.getAudioInputStream(url);

            Clip clip = AudioSystem.getClip();

            clip.open(audioIn);

            clip.start();
            clip.loop(5);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // Set up the initial positions of all space objects
    private void configureGameData()
    {
        // Configure the play area size
        SpaceObject.playfieldWidth = playWidth;
        SpaceObject.playfieldHeight = playHeight;

        // Create the ship
        ship = new Ship(playWidth/2, playHeight/2, 0, 0);

        // Create the shot vector (initially, there shouldn't be any shots on the screen)
        shots = new Vector<Shot>();

        // Read the comets from comets.cfg
        comets = new Vector<Comet>();

        try
        {
            Scanner fin = new Scanner(new File("comets.cfg"));

            // Loop through each line of the file to read a comet
            while(fin.hasNext())
            {
                String cometType = fin.next();
                double xpos = fin.nextDouble();
                double ypos = fin.nextDouble();
                double xvel = fin.nextDouble();
                double yvel = fin.nextDouble();

                if(cometType.equals("Large"))
                    comets.add(new LargeComet(xpos, ypos, xvel, yvel));
                else if(cometType.equals("Medium")){
                    comets.add(new MediumComet(xpos, ypos, xvel, yvel));
                }
                else 
                    comets.add(new SmallComet(xpos, ypos, xvel, yvel));
            }
        }
        // If the file could not be read correctly for whatever reason, abort
        // the program
        catch(FileNotFoundException e)
        {
            System.err.println("Unable to locate comets.cfg");
            System.exit(0);
        }
        catch(Exception e)
        {
            System.err.println("comets.cfg is not in a proper format");
            System.exit(0);
        }
    }

    // Set up the game window
    private void configureGUI()
    {
        // Load Images & Icons
        // Background Image
        try {
            background = ImageIO.read(this.getClass().getClassLoader().getResource("galaxy.jpg"));
        } catch (IOException e) {
        }

        // Space Ship Image
        try {
            spaceShip = ImageIO.read(this.getClass().getClassLoader().getResource("ship.png"));
        } catch (IOException e) {
        }

        // Large Comet Image
        try {
            largeComet = ImageIO.read(this.getClass().getClassLoader().getResource("largecomet.png"));
        } catch (IOException e) {
        }

        // Medium Comet Image
        try {
            mediumComet = ImageIO.read(this.getClass().getClassLoader().getResource("mediumcomet.png"));
        } catch (IOException e) {
        }

        // Medium Comet Image
        try {
            smallComet = ImageIO.read(this.getClass().getClassLoader().getResource("smallcomet.png"));
        } catch (IOException e) {
        }

        // bullet Image
        try {
            bullet = ImageIO.read(this.getClass().getClassLoader().getResource("bullet.png"));
        } catch (IOException e) {
        }





        // Create the window object
        frame = new JFrame("Comets");
        frame.setSize(playWidth+20, playHeight+35);
        frame.setResizable(false);


        // The program should end when the window is closed
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);



        frame.setSize(playWidth, playHeight);
        // Set the window's layout manager
        frame.setLayout(new FlowLayout());



        // Create background
        JLabel bgLabel = new JLabel( new ImageIcon(background.getScaledInstance(playWidth, playHeight, 0) ) );
        bgLabel.setSize(playWidth, playHeight);
        frame.setContentPane(bgLabel);
        frame.pack();



        // Create the play area
        playArea = new JPanel();
        playArea.setSize(playWidth, playHeight);
        playArea.setFocusable(false);
        playArea.setOpaque(false);
        frame.add(playArea);






        // Make the frame listen to keystrokes
        frame.addKeyListener(this);


    }


    // The main game loop. This method coordinates everything that happens in
    // the game
    private void playGame()
    {


        while(true)
        {
            // Measure the current time in an effort to keep up a consistent
            // frame rate
            long time = System.currentTimeMillis();

            // If the ship has been dead for more than 3 seconds, revive it
            if(shipDead && shipTimeOfDeath + 3000 < time)
            {
                shipDead = false;
                ship = new Ship(playWidth/2, playHeight/2, 0, 0);
            }

            // Process game events, move all the objects floating around,
            // and update the display
            if(!shipDead)
            handleKeyEntries();
            handleCollisions();
            moveSpaceObjects();

            // Sleep until it's time to draw the next frame 
            // (i.e. 32 ms after this frame started processing)
            try
            {
                long delay = Math.max(0, 32-(System.currentTimeMillis()-time));

                Thread.sleep(delay);
            }
            catch(InterruptedException e)
            {
            }

        }
    }

    // Deal with objects hitting each other
    private void handleCollisions()
    {
        // Anything that is destroyed should be erased, so get ready
        // to erase stuff
        Graphics g = playArea.getGraphics();
        Graphics2D g2d = (Graphics2D) g;
        g2d.setComposite(AlphaComposite.getInstance(type, alpha));

        // Deal with shots blowing up comets
        for(int i = 0; i < shots.size(); i++)
        {
            Shot s = shots.elementAt(i);
            for(int j = 0; j < comets.size(); j++)
            {
                Comet c = comets.elementAt(j);

                // If a shot has hit a comet, destroy both the shot and comet
                if(s.overlapping(c))
                {

                    // Erase the bullet
                    shots.remove(i);
                    i--;
                    repaint((int)shots.elementAt(i).getXPosition(), (int)shots.elementAt(i).getYPosition(), (int)(2*shots.elementAt(i).getRadius()), (int)(2*shots.elementAt(i).getRadius()));

                    // If the comet was actually destroyed, replace the comet
                    // with the new comets it spawned (if any)
                    Vector<Comet> newComets = c.explode();

                    if(newComets != null)
                    {

                        paintComponent(g);
                        comets.remove(j);
                        j--;
                        comets.addAll(newComets);
                    }
                    break;
                }
            }
        }

        // Deal with comets blowing up the ship
        if(!shipDead)
        {
            for(Comet c : comets)
            {
                // If the ship hit a comet, kill the ship and mark down the time 
                if(c.overlapping(ship))
                {
                    shipTimeOfDeath = System.currentTimeMillis();
                    shipDead = true;
                    spaceObject=ship;
                    paintComponent(g);
                }
            }
        }
    }

    // Check which keys have been pressed and respond accordingly
    private void handleKeyEntries()
    {
        // Ship movement keys
        if(accelerateHeld)
            ship.accelerate();

        if(slowDownHeld)
            ship.slowDown();

        // Shooting the cannon
        if(firing)
        {
            firing = false;
            shots.add(ship.fire());
        }


    }

    // Deal with moving all the objects that are floating around
    private void moveSpaceObjects()
    {
        Graphics g = playArea.getGraphics();


        // Handle the movements of all objects in the field
        if(!shipDead)
        updateShip(g);
        updateShots(g);
        updateComets(g);        
    }

    // Move all comets and draw them to the screen
    private void updateComets(Graphics g)
    {


        for(Comet c : comets)
        {
            spaceObject=c;
            paintComponent(g);

            // Move the comet to its new position
            c.move();


            paintComponent(g);

        }
    }


    // Move all shots and draw them to the screen
    private void updateShots(Graphics g)
    {

        for(int i = 0; i < shots.size(); i++)
        {
            Shot s = shots.elementAt(i);

            // Erase the shot at its old position
            paintComponent(g);


            // Move the shot to its new position
            s.move();

            // Remove the shot if it's too old
            if(s.getAge() > 180)
            {
                shots.remove(i);
                i--;
            }
            // Otherwise, draw it at its new position
            else
            {
                moveImage(g, s, (int)s.getXPosition(), (int)s.getYPosition());
                paintComponent(g);
            }       
        }
    }


    // Moves the ship and draws it at its new position
    private void updateShip(Graphics g)
    {
        // Erase the ship at its old position
        paintComponent(g);

        // Ship rotation must be handled between erasing the ship at its old position
        // and drawing it at its new position so that artifacts aren't left on the screen
        if(turnLeftHeld)
            ship.rotateLeft();
        if(turnRightHeld)
            ship.rotateRight();
        ship.move();

        // Draw the ship at its new position
        moveImage(g, ship, (int)ship.getXPosition(), (int)ship.getYPosition());
        paintComponent(g);
    }

    // Draws this ship s to the specified graphics context
    private void drawShip(Graphics g, Ship s)
    {




        Graphics2D ship = (Graphics2D) spaceShip.getGraphics();

        double x = Math.sin(s.getAngle());
        double y = Math.cos(s.getAngle());


        AffineTransform transformsave = AffineTransform.getRotateInstance(x, y, spaceShip.getWidth()/2, spaceShip.getHeight()/2);
        AffineTransformOp transform = new AffineTransformOp( transformsave, AffineTransformOp.TYPE_BILINEAR );


        // Figure out where the ship should be drawn
        int xCenter = (int)s.getXPosition();
        int yCenter = (int)s.getYPosition();



        // Draw the ship body
        g.drawImage(transform.filter(spaceShip, null), xCenter-10, yCenter-20, null);
        ship.setTransform(transformsave);


    }

    public void setSpaceObject(SpaceObject s){
        spaceObject=s;
    }

    public SpaceObject getSpaceObject(){
        return spaceObject;
    }

    @Override
    protected void paintComponent( Graphics g ){
        super.paintComponent(g);

        spaceObject=getSpaceObject();
        int radius = (int)s.getRadius();
        int xCenter = (int)s.getXPosition();
        int yCenter = (int)s.getYPosition();

        // Draw the object
        if(spaceObject==s)
            g.drawImage( bullet, xCenter-radius, yCenter-radius, this );
        else if(spaceObject==large)
            g.drawImage( largeComet, xCenter-radius, yCenter-radius, this );
        else if(spaceObject==medium)
            g.drawImage( mediumComet, xCenter-radius, yCenter-radius, this );
        else if(spaceObject==small)
            g.drawImage( smallComet, xCenter-radius, yCenter-radius, this );
        else if(spaceObject==ship)
            drawShip(g, ship);



    }

    public void moveImage(Graphics g, SpaceObject s, int x, int y){
        int radius = (int)s.getRadius();
        int xCenter=0, yCenter=0;



        if(xCenter!=x || yCenter!=y){

            xCenter= (int)s.getXPosition();
            yCenter = (int)s.getYPosition();
            repaint(xCenter, yCenter, radius*2, radius*2);
        }


    }



    // Deals with keyboard keys being pressed
    public void keyPressed(KeyEvent key)
    {
        // Mark down which important keys have been pressed
        if(key.getKeyCode() == KeyEvent.VK_UP)
            this.accelerateHeld = true;
        if(key.getKeyCode() == KeyEvent.VK_LEFT)
            this.turnLeftHeld = true;
        if(key.getKeyCode() == KeyEvent.VK_RIGHT)
            this.turnRightHeld = true;
        if(key.getKeyCode() == KeyEvent.VK_SPACE)
            this.firing = true;
        //ADD DOWN TO SLOW DOWN SHIP!!!
        if(key.getKeyCode() == KeyEvent.VK_DOWN)
            this.slowDownHeld = true;
    }

    // Deals with keyboard keys being released
    public void keyReleased(KeyEvent key)
    {
        // Mark down which important keys are no longer being pressed
        if(key.getKeyCode() == KeyEvent.VK_UP)
            this.accelerateHeld = false;
        if(key.getKeyCode() == KeyEvent.VK_LEFT)
            this.turnLeftHeld = false;
        if(key.getKeyCode() == KeyEvent.VK_RIGHT)
            this.turnRightHeld = false;
        //ADD DOWN TO SLOW DOWN SHIP!!!
        if(key.getKeyCode() == KeyEvent.VK_DOWN)
            this.slowDownHeld = false;
    }

    // This method is not actually used, but is required by the KeyListener interface
    public void keyTyped(KeyEvent arg0)
    {
    }





    public static void main(String[] args)
    {



        // A GUI program begins by creating an instance of the GUI
        // object. The program is event driven from that point on.
        new CometsMain();



    }

}
Zong
  • 5,701
  • 5
  • 27
  • 46
kourtney b
  • 11
  • 3

2 Answers2

2

Don't use getGraphics. The problem with this, is it's simply a snap shot of what was last painted.

This is like taking a sheet of paper and repeatedly drawing on top of it, it makes a real mess real quick.

getGraphics can also return null.

In Swing painting is controlled by the RepaintManager which makes decisions about what and when things should be painted.

The basic concept you should be trying to follow is to...

  • Update the state of the game model
  • Update the view of the game model
  • Render the view to the screen

This can be achieved in a few different ways, but to start with, if you want to make updates to the UI, you should override the paintComponent method of a component that extends from something like JComponent.

When called, call super.paintComponent which will automatically prepare the Graphics context for painting.

To update the view...

You Could...

  • In a background thread, update the game model and request that the view be repainted
  • In the view's paintComponent method, re-draw the model

This is a relatively simple approach, but can, if not controlled well, can get the view and the model out of sync. You also need to ensure that the model is not changed while the view is been updated...

Or, You Could...

  • Create two (or more) BufferedImages
  • In a background thread, update the game model
  • In the background thread, update the "offscreen" buffer to represent the current game model state
  • Switch the buffered images (moving the "offscreen" buffer to the "active screen" and the "active screen" to the "offscreen")
  • Request that the view be updated
  • In the view's paintComponent method, simply draw the "active screen"

This is a more complex process, which will require you to ensure that the buffer's are the same size as the view, but means that the model can be updated independently of the view repaints. There is still the danger that you could change the "off" and "active" screen buffers while the view is been painted.

You could elevate this process slightly by using some kind of queue, where you place BufferedImages that can be used for rendering to (by popping them off the queue) and having the "view" push them back again once it has rendered it...

Or some combination of these, where you lock the switching of the "active" and "off" screen buffers to ensure that that "active" buffer isn't being painted.

Take a look at Performing Custom Painting and Painting in AWT and Swing for more details

For example...

Community
  • 1
  • 1
MadProgrammer
  • 323,026
  • 21
  • 204
  • 329
0

Modern graphics applications use following approach:

For every frame repeat these steps

  1. erase screen
  2. draw background
  3. draw your objects in proper order
  4. do anything else

With this approach you don't need to track previous locations of your objects that can be very tricky because object may overlap.

Of course this will cause flickering, due to lack of performance. Various algorithms to prevent this are exists, take a look on this Documentation and this Question

Community
  • 1
  • 1
Viktor Aseev
  • 698
  • 4
  • 9