1

我正在尝试Graphics2D在 JPanel 中设计一个简单的游戏。我可以通过覆盖该paintComponent()方法来绘制普通对象。但是当我在孤立线程中引用 Graphics2D 对象时,它不起作用。我哪里错了?

public void paintComponent(Graphics g) {

    super.paintComponent(g);
    g2d = (Graphics2D) g;

    g2d.drawString("sample",60,100); //Works fine

    if(<Certain Condition>){
       new Thread(new Runnable(){
            //Some Code Here
            public void run() {
               try{
                 g2d.drawString("sample2",60,100); //Does not work.. :(
                 System.out.println("Test Print"); //Shows Output
               }
               catch (Exception e)
               {
               }
             }
       }).start();
   }
}

这是完整的代码供参考。这本质上是一个“乒乓球”游戏。它运作良好,但我无法强调当球击中前锋时得分的增加。代码的重要部分被突出显示。这是SSCCE。

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

import java.util.Random;

public class MovingBall extends JPanel {
    int XPos, YPos;
    int speedX, speedY;
    int diameter;
    private JButton jButton1 = new JButton();
    private JButton jButton2 = new JButton();
    private JLabel jLabel1 = new JLabel();
    private static Timer timer;
    private static MovingBall movingball;
    private int w,h;

    private int strikerHeight;
    private int strikerWidth;

    private int score;
    private boolean isBallMoving;

    int strikerYPos;
    Graphics2D g2d;

    public MovingBall() {

        //Striker Properties
        strikerHeight = 100;
        strikerWidth = 20;
        strikerYPos = strikerHeight/2;

        //Ball Properties
        isBallMoving = false;
        XPos = strikerWidth + 5;
        YPos = 0;
        Random r = new Random();
        speedX = 2+ Math.abs(r.nextInt()) % 5;
        speedY = 2+ Math.abs(r.nextInt()) % 5;
        diameter = 50;

        //UI Objects
        try {
            jbInit();
        } catch (Exception e) {
            e.printStackTrace();
        }

        movingball = this; //Helps to access the current class object in inner classes

        //Create a timer for animation
        timer = new Timer(1, new ActionListener() {
            public void actionPerformed(ActionEvent evt) {
                movingball.repaint();
            }    
        });
        timer.start();
    }

    public void paintComponent(Graphics g) {

        super.paintComponent(g);
        g2d = (Graphics2D) g;

        Dimension size = getSize();
        Insets insets = getInsets();

        w =  size.width - insets.left - insets.right;
        h =  size.height - insets.top - insets.bottom;

        //Paint the striker
        g2d.setColor(Color.DARK_GRAY);
        if(strikerYPos < strikerHeight/2) //Top End
            g2d.fillRect(0,0, strikerWidth, strikerHeight);
        else if(strikerYPos > (h-strikerHeight/2)) //Bottom End
            g2d.fillRect(0,h-strikerHeight, strikerWidth, strikerHeight);
        else //Anywhere in the middle
            g2d.fillRect(0,strikerYPos - (strikerHeight/2), strikerWidth, strikerHeight);

        //Paint the ball
        if (isBallMoving) {
            XPos += speedX;
            YPos += speedY;

            g2d.drawOval(XPos, YPos, diameter,diameter);

            if((XPos+diameter) >= w)
            {
                //speedX *= -1;
                speedX = ((int)Math.signum((double)speedX))*(-1) * (2+ Math.abs(new Random().nextInt()) % 5);
                XPos = w-diameter-1;
            }
            if(XPos <= strikerWidth)
            {
                if((YPos+diameter/2) >= (strikerYPos-strikerHeight/2) && (YPos+diameter/2) <= (strikerYPos+strikerHeight/2))
                {
                    score++;

                    //////////////////////////////////////////////////////////////////////
                    /////THIS IS THE PART TO FOCUS ON///////////////////////////////////////
                    /////WHEN THE BALL HITS THE STRIKER, I SHOW A '+1' TEXT FADING UPWARDS FROM THE POINT OF HIT
                    /////(THIS IS TO HIGHLIGHT A +1 INCREASE IN SCORE)///////////////////
                    //////NOW SINCE THE BALL MAY HIT THE STRIKER AGAIN BEFORE THE PREVIOUS +1 HAS COMPLETELY FADED,
                    //////I HAVE MADE THIS SIMPLE THREAD TO CREATE A +1 EVERY TIME THERE IS A HIT. SO THERE CAN BE MULTIPLE
                    //////+1 ON THE SCREEN.
                    //-------------------------------SADLY, SOMETHING IS WRONG-------------------

                    //Print a '+1' to show score increase
                    new Thread(new Runnable(){
                        int yStart = strikerYPos;
                        int fadeLength = 0;
                        Timer pointTimer;
                        int MAX_FADE_LEN = 50;

                        public void run() {
                            try
                            {

                                pointTimer = new Timer(1, new ActionListener() {
                                         public void actionPerformed(ActionEvent evt) {
                                            if(fadeLength >= MAX_FADE_LEN)
                                                pointTimer.stop();
                                            g2d.setColor(new Color(0,0,0,255));
                                            g2d.setFont(new Font("Times",Font.BOLD,20));
                                            g2d.drawString("+1",60,yStart - fadeLength);
                                            g2d.drawOval(100,100,50,50);
                                            System.out.println("Drawn +1 at x = " + 60 + " y = " + (yStart - fadeLength));
                                            fadeLength++;
                                         }    
                                        });
                                pointTimer.start();
                            }
                            catch (Exception e)
                            {

                            }
                        }

                    }).start();
                    ////////////////THREAD ENDS HERE//////////////////////
                }
                else
                {
                    score--;
                }

                //SHOW THE SCORE ON THE LABEL
                jLabel1.setText("Score: " + score);
                speedX = ((int)Math.signum((double)speedX))*(-1) * (2+ Math.abs(new Random().nextInt()) % 5);
                XPos = strikerWidth+1;
            }

            if(YPos <= 0)
            {
                speedY = ((int)Math.signum((double)speedY))*(-1) * (2+ Math.abs(new Random().nextInt()) % 5);
                YPos = 0;
            }
            if((YPos+diameter) >= h)
            {
                speedY = ((int)Math.signum((double)speedY))*(-1) * (2+ Math.abs(new Random().nextInt()) % 5);
                YPos = h-diameter;
            }
        } else {
            g2d.drawOval(XPos,YPos,diameter,diameter);
            return;
        }
    }

    public static void main(String[] args) {

        JFrame frame = new JFrame("Magic Ball");
        movingball = new MovingBall();
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(movingball);
        frame.setSize(450, 700);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    private void jbInit() throws Exception {
        jButton1.setText("Start");
        jButton1.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        jButton1_actionPerformed(e);
                    }
                });
        jButton2.setText("Stop");
        jButton2.addActionListener(new ActionListener() {
                    public void actionPerformed(ActionEvent e) {
                        jButton2_actionPerformed(e);
                    }
                });
        jLabel1.setText("Score:0");
        this.add(jButton1, null);
        this.add(jButton2, null);
        this.add(jLabel1, null);
        this.setBackground(Color.white);
        this.addMouseMotionListener(new MouseMotionListener() {
                    public void mouseMoved(MouseEvent e) {
                        int coordX = e.getX();
                        if(coordX < 200)
                            strikerYPos = e.getY();
                    }

                    public void mouseDragged(MouseEvent e) {
                    }
                });
    }

    private void jButton1_actionPerformed(ActionEvent e) {
        if(!isBallMoving)
            isBallMoving = true;
    }

    private void jButton2_actionPerformed(ActionEvent e) {
        isBallMoving = false;
    }
}
4

5 回答 5

2
  • 在 API 中实现的每个鼠标、按键和内部方法上,paintComponent 中的所有内容都会(自动)重绘,然后你的线程可能永远不会结束,可能会有一堆并发线程,没有任何东西被重绘,显示

  • 必须在 EDT 上完成向 Swing GUI 的输出

  • 使用 Swing Timer 而不是 new Thread(new Runnable(){

  • 调用重绘()

于 2013-05-14T13:58:33.403 回答
1

据我了解-您将 Graphics2D 对象保存到g2d变量中并稍后尝试从单独的线程中对其进行绘制?如果是这样 - 不要这样做。这是一件非常糟糕的事情。真的。

如果您想修改(动画/更改)在组件上绘制的任何内容 - 只需更改影响绘制的数据/模型,然后重新绘制整个组件或其修改部分(组件边界内的任何矩形)。

例如,在您的示例案例中 - 将绘制的字符串坐标保留在paint方法之外并在单独的线程中修改它们,然后每次更改它们时只需在组件上调用 repaint 。每个重绘字符串都将在更新的坐标处绘制。

另请注意,可能会在 EDT(事件调度线程)之外调用重绘,因为无论如何它都会在 EDT 中执行实际的重绘。

这是一些随机的动画示例:

public class AnimationTest
{
    private static List<Point> locationData = new ArrayList<Point> ();
    private static List<Boolean> directionData = new ArrayList<Boolean> ();

    public static void main ( String[] args )
    {
        locationData.add ( new Point ( 5, 25 ) );
        directionData.add ( true );

        final JComponent canvas = new JComponent ()
        {
            protected void paintComponent ( Graphics g )
            {
                super.paintComponent ( g );

                Graphics2D g2d = ( Graphics2D ) g;

                for ( int i = 0; i < locationData.size (); i++ )
                {
                    Point p = locationData.get ( i );
                    g2d.drawString ( "Some string #" + i, p.x, p.y );
                }
            }
        };

        canvas.addMouseListener ( new MouseAdapter ()
        {
            public void mousePressed ( MouseEvent e )
            {
                locationData.add ( e.getPoint () );
                directionData.add ( true );
                canvas.repaint ();
            }
        } );

        JFrame frame = new JFrame ();
        frame.getContentPane ().setLayout ( new BorderLayout () );
        frame.getContentPane ().add ( canvas );
        frame.setSize ( 500, 500 );
        frame.setLocationRelativeTo ( null );
        frame.setDefaultCloseOperation ( JFrame.EXIT_ON_CLOSE );
        frame.setVisible ( true );

        Timer timer = new Timer ( 1000 / 48, new ActionListener ()
        {
            public void actionPerformed ( ActionEvent e )
            {
                for ( int i = 0; i < locationData.size (); i++ )
                {
                    Point p = locationData.get ( i );
                    if ( directionData.get ( i ) )
                    {
                        if ( p.y < canvas.getHeight () - 1 )
                        {
                            p.y += 1;
                        }
                        else
                        {
                            directionData.set ( i, false );
                        }
                    }
                    else
                    {
                        if ( p.y > 20 )
                        {
                            p.y -= 1;
                        }
                        else
                        {
                            directionData.set ( i, true );
                        }
                    }
                }
                canvas.repaint ();
            }
        } );
        timer.start ();
    }
}

你可以在这里找到:

  1. 绘画和动画基于单个初始元素的数据列表
  2. 通过鼠标交互修改数据
  3. 对任何数据更改进行适当的画布更新

示例没有太优化,但应该足以理解概念。

于 2013-05-14T14:16:50.780 回答
1

我不认为很多人会认为将近 250 LOC 是“短的”(尽管我必须承认我在编写 SSCCE 文档时故意含糊其辞)。OTOH 我将这里看到的较短的源代码改编为一个动画示例,该示例显示了鼠标点击的“淡入淡出效果”。让它适应您的需求留给..you 的练习。

此源显示如何在 5 秒内更改绘制的字符串。Thread它对主要(弹跳球)和淡入淡出动画使用相同的(EDT)。

褪色的罢工

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.*;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import javax.swing.*;

class ShapeCollision {

    private BufferedImage img;
    private Area walls;
    int x;
    int y;
    int xDelta = 3;
    int yDelta = 2;
    ArrayList<Strike> strikes;

    /**
     * A method to determine if two instances of Area intersect
     */
    public boolean doAreasCollide(Area area1, Area area2) {
        boolean collide = false;

        Area collide1 = new Area(area1);
        collide1.subtract(area2);
        if (!collide1.equals(area1)) {
            collide = true;
        }

        Area collide2 = new Area(area2);
        collide2.subtract(area1);
        if (!collide2.equals(area2)) {
            collide = true;
        }

        return collide;
    }

    ShapeCollision() {
        int w = 400;
        int h = 200;
        img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
        final JLabel imageLabel = new JLabel(new ImageIcon(img));
        x = w / 2;
        y = h / 2;

        strikes = new ArrayList<Strike>();

        MouseListener strikeListener = new MouseAdapter() {

            @Override
            public void mouseClicked(MouseEvent e) {
                Strike s = new Strike(e.getPoint(),System.currentTimeMillis());
                strikes.add(s);
            }
        };
        imageLabel.addMouseListener(strikeListener);

        walls = new Area(new Rectangle(0, 0, w, h));

        ActionListener animate = new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                animate();
                imageLabel.repaint();
            }
        };
        Timer timer = new Timer(50, animate);

        timer.start();
        JOptionPane.showMessageDialog(null, imageLabel);
        timer.stop();
    }

    public void animate() {
        Graphics2D g = img.createGraphics();
        g.setRenderingHint(
                RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);

        g.setColor(Color.BLACK);
        g.fillRect(0, 0, img.getWidth(), img.getHeight());
        x += xDelta;
        y += yDelta;
        int s = 15;
        Area player = new Area(new Ellipse2D.Double(x, y, s, s));

        // Acid test of edge collision;
        if (doAreasCollide(player, walls)) {
            if (x + s > img.getWidth() || x < 0) {
                xDelta *= -1;
            }
            if (y + s > img.getHeight() || y < 0) {
                yDelta *= -1;
            }
        }
        g.setColor(Color.ORANGE);
        g.setColor(Color.YELLOW);
        g.fill(player);

        for (Strike strike : strikes) {
            strike.draw(g);
        }

        g.dispose();
    }

    public static void main(String[] args) {
        Runnable r = new Runnable() {

            @Override
            public void run() {
                new ShapeCollision();
            }
        };
        // Swing GUIs should be created and updated on the EDT
        // http://docs.oracle.com/javase/tutorial/uiswing/concurrency/initial.html
        SwingUtilities.invokeLater(r);
    }
}

class Strike {

    private Point point;
    private long started;
    private final long DURATION = 5000;
    private boolean expired = false;

    Strike(Point point, long time) {
        this.point = point;
        started = time;
    }

    public void draw(Graphics g) {
        long now = System.currentTimeMillis();
        long age = now - started;
        if (age>DURATION) {
            expired = true;
            return;
        }
        double fraction = 1d-((double)age/(double)DURATION);
        int alpha = (int)(fraction*255d);
        Color c = new Color(255,255,255,alpha);
        g.setColor(c);
        String s = point.x + "," + point.y;
        g.drawString( s, point.x, point.y );
    }

    public boolean isExpired() {
        return expired;
    }
}
于 2013-05-15T11:12:18.463 回答
0

Swing 是一个独立于平台的、用于 Java 的模型-视图-控制器 GUI 框架,它遵循单线程编程模型。

您应该使用调度程序。http://en.wikipedia.org/wiki/Event_Dispatch_Thread

于 2013-05-14T17:51:47.880 回答
0

打印图形对象,如果它在调用之间发生变化,paintComponent则意味着您传递给孤立线程的对象是一个死对象,swing 不再用于在当前组件上进行绘制。

于 2013-05-14T14:05:11.387 回答