Java - Pac-Man - GUI - 绘图问题,以及给有抱负的程序员的一般提示

Posted

技术标签:

【中文标题】Java - Pac-Man - GUI - 绘图问题,以及给有抱负的程序员的一般提示【英文标题】:Java - Pac-Man - GUI - Drawing Graphics issue, and general tips for an aspiring programmer 【发布时间】:2014-12-03 16:00:56 【问题描述】:

我正在制作吃豆人,但在框架上绘制图形时遇到问题,当我绘制点图像时,它看起来像蛇游戏,我尝试将背景和字符的绘制方法都放在渲染中方法,但比我的点图像闪烁

它目前的样子,随意忽略它是一个内部笑话的随机面孔。

这也是我的第一个游戏,所以任何关于结构的提示、关于我做对什么(如果有的话)和我做错什么的提示以及一般提示都会非常有帮助!

我也知道我有几个未使用的方法

代码:


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);
    

【问题讨论】:

【参考方案1】:

getGraphics 不是自定义绘画的完成方式。在您的情况下,您应该覆盖paint 方法,并确保在进行任何自定义绘画之前调用super.paint

getGraphics 返回上次用于绘制组件的 Graphics 上下文,它可以在下一个绘制周期被丢弃,为 null 或不再被组件使用

请记住,绘画使用“画家画布”方法,也就是说,就像在物理画布上绘画一样,当您在画布上绘画时,您会在先前存在的内容上绘画,但不会擦除它。

现在,如果你覆盖paint,你会发现你会有一个闪烁的问题。这是因为Canvas 不是双缓冲的

要解决这个问题,您应该考虑用户 BufferStrategy,它不仅允许您生成多个要绘制的缓冲区,还可以控制绘制过程本身

只是不要忘记在绘制之前清除每个缓冲区...

【讨论】:

【参考方案2】:

Double buffering 是让您拥有无闪烁动画的技巧。基本上,您的画布有两种表示形式,一种是当前正在显示的,另一种是您可以在上面绘制的。如果您完成了绘图,则将绘图画布复制到显示画布上。根据系统和硬件,您可以通过更优雅的方式告诉硬件切换画布(翻页)。

如果没有双缓冲或类似的技术,几乎不可能有无闪烁的动画。

使用双缓冲,您可以先绘制背景,然后再绘制前景精灵。仅绘制已被前景精灵破坏的背景部分可能更有效(也有各种技术,包括在绘制精灵之前拍摄受影响区域的快照图像)。

你可以找到一个简单的example for Java double buffering here。 Java 的BufferStrategy 是一个更复杂的解决方案,可以使用硬件功能进行翻页。

【讨论】:

【参考方案3】:

我认为问题在于您只在图像背景上绘图,而从不从图像中删除旧绘图。您将需要清除该区域,然后开始绘制以获得您想要的结果。

我从未尝试过制作游戏,但当我制作简单动画时,我通常会在 JFrameJPanel 上制作它们。使用JFrame,您可以覆盖paint() 方法,使用JPanel,可以覆盖paintComponent() 方法。它有助于保持我正在绘制的所有内容集中,这使我更容易修改我的代码。当您在覆盖的方法中调用相应的 super 方法时,它会以全新的方式开始,这意味着您将不得不重新绘制(图像)背景和角色。如果您决定在 JFrame/JPanel 上添加任何内容,也需要调用 super 方法来绘制该组件的子组件。

如果您选择使用上述其中一种,那么我会推荐 JPanel,因为它提供双缓冲,这将有助于使您的动画看起来流畅/流畅。另外,不要忘记致电repaint();

这里有一个简单的例子,如果你注释掉super.paintComponent(g);,它可以用来复制你的问题。

*请注意,我在此示例中扩展并使用了 JPanel。

代码:

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.
    

编辑:

如果您有很多事情要做并且不想将它们全部添加到您的 paintComponent(); 方法中,您可以为各种事物创建一个方法并从您的 Overriden 绘制方法中调用它们,您也可以传递它们你的图形对象。它将帮助您保持相对简单。

【讨论】:

请记住,您不能控制 Swing 中的绘制过程,paintComponent 可以随时调用,这意味着您应该只绘制模型的当前状态。另外,请注意,Swing 不是线程安全的,因此如果 OP 打算使用自己的计时线程,他们将需要在模型和 paintComponent 方法之间提供同步,以防止可能出现的脏画...

以上是关于Java - Pac-Man - GUI - 绘图问题,以及给有抱负的程序员的一般提示的主要内容,如果未能解决你的问题,请参考以下文章

java GUI Graphics2D 绘图

本周新学的 GUI绘图技术

Pac-Man 吃豆人

matlab gui 怎样在指定的轴绘图

全连接神经网络实现(玩Pac-Man游戏)详解

Python GUI 中的实时绘图