1

I am making Pac-Man and I'm having trouble with drawing graphics on a frame, when i draw my point image it looks like a game of snake, i tried putting my drawing methods for background and char both in the render method, but than my point image flickers

What it currently looks like, feel free to ignore the random face it was an inside joke.

What it looks like

Also this is my very first game so any tips on structure, pointers on what I am doing right (if anything) and what I'm doing wrong, and general tips would be extremely helpful!

Also I am aware that i have a couple unused methods

Code:


package game;

import graphics.map;

import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JButton;
import javax.swing.JFrame;



public class main extends Canvas implements Runnable{
    private static final long serialVersionUID = 1L; //not sure why it wanted me to do this, maybe ask bender, or just google it later

    public static boolean running = false;
    public static int HEIGHT = 800;
    public static int WIDTH = 600;
    public static int posX = 50;
    public static int posY = 50;
    public static final String name = "Pac Man Alpha 1.4";
    private static final double speed = 1.2;

    public input input;

    static BufferedImage background = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);;
    static BufferedImage pacman = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);;
    static BufferedImage settingsBackground = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);;
    static BufferedImage level1 = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);;
    static BufferedImage level2 = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);;

    static BufferedImage points = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);;
    static BufferedImage point = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);;


    static JFrame frame;
    private input keypress = new input();
    private map map;

    private static boolean charLoaded = false;
    public static boolean MAIN_MENU = true;
    public static boolean GAME = false;
    public static boolean level1test = true;
    public static boolean level2test = false;
    public static boolean level3test = false;
    public static boolean level4test = false;


    static boolean drawn = false;

    public static boolean key_down;

    public static boolean key_up;

    public static boolean key_right;

    public static boolean key_left;

    //private Screen screen;
    JButton startButton = new JButton("Start"); //Start
    JButton settingsButton = new JButton("Settings"); //Settings
    JButton exitButton = new JButton("Exit"); //Exit


    public main() 
    {


        setMinimumSize(new Dimension(WIDTH , HEIGHT ));
        setMaximumSize(new Dimension(WIDTH , HEIGHT )); // keeps the canvas same size
        setPreferredSize(new Dimension(WIDTH, HEIGHT));

        frame = new JFrame(name);


        if(MAIN_MENU == true && GAME == false){
        buttons(frame.getContentPane());
        }

        frame.setLayout(new BorderLayout());
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); // ends program on
                                                                // close
        frame.addKeyListener(new input() );
        frame.add(this, BorderLayout.CENTER);
        frame.pack(); // keeps size correct
        frame.setResizable(false);
        frame.setVisible(true);
        this.addKeyListener(keypress);

    }

    public static void main(String[] args) 
    {
        try {
            background = ImageIO.read(new File("res\\Background.png"));     
            pacman = ImageIO.read(new File("res\\pacmansprites.png"));
            settingsBackground = ImageIO.read(new File("res\\Background.png"));
            level1 = ImageIO.read(new File("res\\level1.png"));
            //level2 = ImageIO.read(new File("res\\level2.png"));


            point = ImageIO.read(new File("res\\Points for pacman.png"));

        } catch (IOException e) {
        }

        running = true;
        new main().start();

    }



    public void run() 
    {
        long lastTime = System.nanoTime();
        double nsPerTick = 1000000000 / 60D;
        long lastTimer = System.currentTimeMillis();
        double delta = 0;

        int frames = 0;
        int ticks = 0;

        while (running == true) {
            long now = System.nanoTime();
            delta += (now - lastTime) / nsPerTick;
            lastTime = now;
            boolean render = false;

            while (delta >= 1) {
                ticks++;
                tick();
                delta -= 1;
                render = true;

            }

                try {
                    Thread.sleep(3);        //keep the Frames from going to high
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            if(render == true){
            frames++;
            render();
            }

            if (System.currentTimeMillis() - lastTimer >= 1000) {
                lastTimer +=1000;
                //System.out.println("Frames: " + frames + "   Ticks: " + ticks); 

                frames = 0;
                ticks = 0;
            }
        }
    }



    public synchronized void start() 
    {
        new Thread(this).start();
        run();
    }




    public synchronized void stop() 
    {
        running = false;

    }



    public void tick()
    {
        if (key_up) posY -= speed / 2;
        if (key_down) posY += speed;
        if (key_left) posX -= speed / 2;
        if (key_right) posX += speed;
    }


    public void render()
    {
        drawn = false;




        if(MAIN_MENU == false && GAME == true)
            {   
                drawMap();
                drawChar();
            }

        else if(MAIN_MENU == false && GAME == false) {
            Graphics g = getGraphics();
            {
                g.drawImage(settingsBackground,0,0,getWidth(),getHeight(),null);
                g.dispose();
            }
        } else {
                Graphics g = getGraphics();{

                    g.drawImage(background,0,0,getWidth(), getHeight(),null);
                    g.dispose(); //kill it  
            }
        }
    }

    public void drawMap(){
        if(level1test == true){
            Graphics g = getGraphics();
            {
        g.drawImage(level1,0,0,getWidth(),getHeight(),null);
        g.dispose();
        }
        }

        if(level2test == true && drawn == false){
            Graphics g = getGraphics();
            {
        g.drawImage(level2,0,0,getWidth(),getHeight(),null);
        }
        g.dispose();
        }
        drawn = true;
    }


    public void drawChar(){
        //drawMap();
        Graphics g = getGraphics();{
            g.drawImage(point,posX,posY,20, 20,null);
            g.dispose();
            revalidate();
        }
    }



    public void begin() {
        if (key_up) System.out.println("up");
        if (key_down) System.out.println("down");
        if (key_left) System.out.println("left");
        if (key_right) System.out.println("right");
    }





    public void loadMap(){
        if(!drawn && level1test){
        }else if(!drawn && level2test){
            //draw 2nd map here
        }else if(!drawn && level3test){
            //draw 3rd map here
        }
}




    public void buttons(Container pane)
    {
        pane.setLayout(null);

        startButton.addActionListener( new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                MAIN_MENU = false;
                GAME = true;
                frame.remove(startButton);
                frame.remove(settingsButton);
                frame.remove(exitButton);
                frame.revalidate();
                drawMap();
                System.out.println("Start Button Clicked");
            }
        } );


        settingsButton.addActionListener( new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                MAIN_MENU = false;
                GAME = false;
                frame.remove(startButton);
                frame.remove(settingsButton);
                frame.remove(exitButton);
                frame.revalidate();
                frame.repaint();
                System.out.println("Settings Button Clicked");
                }
        } );



        exitButton.addActionListener( new ActionListener() {
            public void actionPerformed(ActionEvent ae) {
                System.out.println("Exit Button Clicked");
                System.exit(0);
            }
        } );



        pane.add(startButton);
        pane.add(settingsButton);
        pane.add(exitButton);


        Insets insets = pane.getInsets();
        Dimension size = startButton.getPreferredSize();

        startButton.setBackground(new Color(0, 0, 0));
        startButton.setForeground(Color.CYAN);
        startButton.setFocusPainted(false);
        startButton.setFont(new Font("Calabri", Font.BOLD, 16));

        settingsButton.setBackground(new Color(0, 0, 0));
        settingsButton.setForeground(Color.RED);
        settingsButton.setFocusPainted(false);
        settingsButton.setFont(new Font("Calabri", Font.BOLD, 16));

        exitButton.setBackground(new Color(0, 0, 0));
        exitButton.setForeground(Color.YELLOW);
        exitButton.setFocusPainted(false);
        exitButton.setFont(new Font("Calabri", Font.BOLD, 16));

        startButton.setBounds((WIDTH - 125) + insets.left, 10 + insets.top,
                size.width + 50, size.height + 10);


        settingsButton.setBounds((WIDTH - 125) + insets.left, 55 + insets.top,
                size.width + 50, size.height + 10);

        exitButton.setBounds((WIDTH - 125) + insets.left, 100 + insets.top,
                size.width + 50, size.height + 10);
    }
}
roke
  • 49
  • 1
  • 9

3 Answers3

3

I think the problem is that you only draw onto the image background, never erasing the old drawing from your image. You will need to clear the area and then start drawing in order to get your desired results.

I have never attempted to make a game but when I do simple animations I usually will do them on a JFrame or JPanel. With a JFrame you can Override the paint() method and with a JPanel, the paintComponent() method. It helps to keep everything that I'm drawing centralized, which makes it much easier for me to modify my code. When you call the respective super method in your overridden method, it will start you off with a clean slate, meaning you will have to paint the (image) background and your characters all over again. Calling the super method is also necessary to paint that component's children if you decide to add anything onto the JFrame/JPanel.

If you chose to use one of the above then I would recommend a JPanel due to it offering double buffering, which will help make your animations look smooth/fluid. Also, do not forget to call repaint();.

Here is a quick example, which can be used to duplicate your issue if you comment out super.paintComponent(g);.

*Note that I am extending and using a JPanel for this example.

Code:

import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class Trial extends JPanel{

    public static void main(String[] args)
    {            
      new Trial();
    }    

    int x = 5; // will represent the x axis position for our crude animation.

    javax.swing.Timer timer = new javax.swing.Timer( 500, new ActionListener(){
        // Timer used to control the animation and
        // the listener is used to update x position and tell it to paint every .5 seconds.

        @Override
        public void actionPerformed(ActionEvent e) {

            x += 5;
            if ( x > 250)
                timer.stop();

            repaint(); // will call the paintComponent method.
        }


     });

    Trial()
    {
        JFrame frame = new JFrame();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);

        frame.add(this);

        frame.setSize(300, 200);
        frame.setVisible(true);
        timer.start();
    }

    @Override
    public void paintComponent(Graphics g)
    {
        super.paintComponent(g); // calling the super paintComponent method to paint children. can comment it
                                 // out to duplicate your error, which happens when the frame isn't "refreshed".

        Graphics2D g2d = (Graphics2D) g.create(); // creating a copy of the graphics object.                                               // I do this to not alter the original 
                                                  // Good practice and also Graphics2D
                                                  // offers a bit more to work with.
        g2d.drawString("Graphics", x, 60); // painting a string.

        g2d.drawRect(x, 80, 10, 10); // painting a rectangle.
    }
}

Edit:

If you have a bunch of stuff to do and don't want to add it all into your paintComponent(); method you could create a method for various things and call them from inside your Overriden paint method, you could also pass them your graphics object. It will help you keep things relatively simple.

MarGar
  • 465
  • 5
  • 12
  • 1
    Just remember that you don't control the paint process within Swing, `paintComponent` could be called at any time, meaning you should only ever paint the current state of the model. Also, beware, that Swing isn't thread safe, so if the OP intends to use their own timing thread, they will need to provide synchronisation between the model and the `paintComponent` method to prevent possible dirty paints... – MadProgrammer Oct 08 '14 at 23:08
3

getGraphics is not how custom painting is done. You should, in your case, override the paint method, and make sure you call super.paint before doing any custom painting.

getGraphics returns the Graphics context last used to paint the component, which could be discarded on the next paint cycle, be null or no longer used by the component

Remember, painting uses the "painters canvas" approach, that is, just like painting in a physical canvas, when you paint into it, you paint over what was previously there, but not erasing it.

Now, if you override paint, you will find that you will have a flickering problem. This is because Canvas is not double buffered

To solve this, you should consider user a BufferStrategy, which allows you to not only generate multiple buffers to paint to, but also to take control of the paint process itself

Just don't forget to clear each buffer before you paint to it...

MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
3

Double buffering is the trick that allows you to have flicker-free animation. Basically you have two representations of your canvas, one that's currently being displayed and one that you can draw on. If you're finished with drawing, you copy the draw-canvas over the display-canvas. Depending on system and hardware there are more elegant ways where you can just tell the hardware to switch canvases (page flipping).

Without double buffering or a similar techniques, it is almost impossible to have flicker-free animation.

With double buffering you can afford to draw the background and then the foreground sprites. It is possibly more efficient to draw only those parts of the background that have been destroyed by the foreground sprites (there are various techniques for that as well, including of taking a snapshot image of the affected areas before you paint the sprites).

You can find a simple example for Java double buffering here. Java's BufferStrategy is a more complex solution that can use hardware features for page flipping.

Community
  • 1
  • 1
Tim Jansen
  • 3,330
  • 2
  • 23
  • 28