如何在自定义 JPanel 中移动精灵?

How do I make a sprite move in a custom JPanel?

如何在自定义 JPanel 中让精灵移动?

我看过类似的问题,虽然有一个问题很相似,但没有解决我的问题。我在 JPanel 中有一个精灵,但我无法让它移动。我对程序必须满足的要求之一是它必须在按下 JButton 时开始移动( 鼠标单击 )。我以我认为应该有效的方式设置了代码,但是当我按下按钮时它会吐出一长串错误。我还需要将面板设为自定义面板 class。

我需要知道的是:

  1. 编程精灵运动的方法(ha)。
  2. 继续无踪迹地移动精灵。
  3. 使 sprite 从面板的边缘反弹。完成(由于没有移动的球而无法测试)

这是我的代码 (MainClient)。

package clientPackage;

import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;

import javax.swing.JFrame;
import javax.swing.JPanel;

import java.awt.Color;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;

import logicPack.Logic;
import javax.swing.JButton;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;

public class ClientClass 
{
Ball mSolo = new Ball();

private JFrame frame;

/**
 * Launch the application.
 */
public static void main(String[] args) {
    EventQueue.invokeLater(new Runnable() {
        public void run() {
            try {
                ClientClass window = new ClientClass();
                window.frame.setVisible(true);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    });
}

/**
 * Create the application.
 */
public ClientClass() 
{
    initialize();

}

/**
 * Initialize the contents of the frame.
 */
Logic Logical;
Graphics g;
private void initialize() {
    frame = new JFrame();
    frame.setBounds(100, 100, 590, 520);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.getContentPane().setLayout(null);

    SpriteField panel = new SpriteField();
    panel.addMouseListener(new MouseAdapter() 
    {

        public void mouseClicked(MouseEvent e) 
        {
        /*  int tX = e.getX();
            Logical.MoveBallX();
            int tY = e.getY();
            Logical.MoveBallY();
            panel.repaint();*/
            Logical.MoveBallX();
            Logical.MoveBallY();
            panel.repaint();
        }
    });
    panel.setForeground(Color.WHITE);
    panel.setBackground(Color.GRAY);
    panel.setBounds(64, 92, 434, 355);
    frame.getContentPane().add(panel);

    JButton btnStart = new JButton("Start");
    btnStart.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) 
        {

            Graphics2D g2 = (Graphics2D)g;
            mSolo.DrawSprite(g2 , Logical.MoveBallX(), Logical.MoveBallY());
        }
    });
    btnStart.setBounds(64, 13, 174, 60);
    frame.getContentPane().add(btnStart);
}
}

这是我的另一个 类 (Logic)

package logicPack;

import clientPackage.Ball;

public class Logic 
{
Ball mSolo;
public int MoveBallX()
{
    int NewX = mSolo.xPos + 50;
    return NewX;
}
public int MoveBallY()
{
    int NewY = mSolo.yPos + 50;
    return NewY;        
}
//Motion, force, friction and collision GO HERE ONLY

}

SpriteField

package clientPackage;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;

import javax.swing.JPanel;

public class SpriteField extends JPanel
{
Ball mSolo;
SpriteField()
{
    mSolo = new Ball();
    repaint();
}

public void paintComponent(Graphics g)
{
    super.paintComponent(g);
    Graphics2D g2 = (Graphics2D)g;
    mSolo.DrawSprite(g2 , mSolo.xPos , mSolo.yPos);

}

}

Ball

package clientPackage;

import java.awt.Color;
import java.awt.Graphics2D;

public class Ball 
{
Ball()
{

}
public int xPos = 25;
public int yPos = 25;
int diameter = 25;
public void DrawSprite(Graphics2D g2, int xPos, int yPos)
{
    g2.setColor(Color.BLACK);
    g2.fillOval(xPos - diameter / 2 , yPos - diameter / 2 , diameter , diameter);

}


}

如果你不明白我的Java评论,你可以忽略它们。 如果您需要更多详细信息来帮助我,请告诉我。

编辑 1: 安德鲁,我能找到的最接近的文章使用箭头键移动精灵。文章 "Sprite not moving in JPanel"。我发现的所有其他文章要么解决了没有 sprite 的 JPanels,要么为 sprite 设置动画。但是,我需要一个被 MouseClicked 的 JButton 来简单地开始移动,并且球不会改变形状或颜色。我相信我的碰撞部分在工作,但在球开始移动之前我无法对其进行测试。

编辑 2: LuxxMiner,感谢您的提示。我使用 getHeight 和 getWidth 方法改进了我的碰撞部分,使其更加准确。

编辑 3: MadProgrammer,谢谢...?问题不在于球的绘制,我无法让球首先移动以重新绘制它。该示例使用箭头键,而不是鼠标单击或 JButton。

首先,看一下 Painting in AWT and Swing and Performing Custom Painting 以了解在 Swing 中绘画是如何工作的。

让我们看一下代码...

您有一个 Ball class,它有自己的属性,但是您的 DrawSprite 方法传入的值覆盖了这些属性?

public class Ball {

    Ball() {
    }
    public int xPos = 25;
    public int yPos = 25;
    int diameter = 25;

    public void DrawSprite(Graphics2D g2, int xPos, int yPos) {
        g2.setColor(Color.BLACK);
        g2.fillOval(xPos - diameter / 2, yPos - diameter / 2, diameter, diameter);

    }

}

这有什么意义? Ball 应该绘制它自己的当前状态。你应该摆脱额外的参数

public class Ball {

    Ball() {
    }
    public int xPos = 25;
    public int yPos = 25;
    int diameter = 25;

    public void DrawSprite(Graphics2D g2) {
        g2.setColor(Color.BLACK);
        g2.fillOval(xPos - diameter / 2, yPos - diameter / 2, diameter, diameter);

    }

}

ClientClassLogicSpriteField 都有自己的 Ball 引用,其中 none 是共享的,所以 if Logic where更新它的状态 BallClientClassSpriteField 都不会真正看到这些变化。

实际上,只有 SpriteField 需要 Ball 的实例,因为它基本上是 "ball container",它具有确定球是否出界所需的信息并且想要要知道什么时候应该重新粉刷球,最好在此时隔离 BallSpriteField 的 functionality/responsibility。

您还需要一种实际移动球的方法。虽然您可以使用其他事件,但如果球自己移动我会很好,为此,您可以使用 Swing Timer,它不会阻塞事件调度线程,但会通知已注册的 ActionListener 在 EDT 的上下文中,从而可以安全地从内部更新 UI。

public class SpriteField extends JPanel {

    private Ball mSolo;
    private Timer timer;

    private int xDelta, yDelta;

    public SpriteField() {
        mSolo = new Ball();

        do {
            xDelta = (int) ((Math.random() * 8) - 4);
        } while (xDelta == 0);
        do {
            yDelta = (int) ((Math.random() * 8) - 4);
        } while (yDelta == 0);
    }

    public void start() {
        if (timer == null || !timer.isRunning()) {
            timer = new Timer(40, new ActionListener() {
                @Override
                public void actionPerformed(ActionEvent e) {
                    mSolo.xPos += xDelta;
                    mSolo.yPos += yDelta;

                    if (mSolo.xPos - (mSolo.diameter / 2) < 0) {
                        mSolo.xPos = mSolo.diameter / 2;
                        xDelta *= -1;
                    } else if (mSolo.xPos + (mSolo.diameter / 2) > getWidth()) {
                        mSolo.xPos = getWidth() - (mSolo.diameter / 2);
                        xDelta *= -1;
                    }
                    if (mSolo.yPos - (mSolo.diameter / 2) < 0) {
                        mSolo.yPos = (mSolo.diameter / 2);
                        yDelta *= -1;
                    } else if (mSolo.yPos + (mSolo.diameter / 2) > getHeight()) {
                        mSolo.yPos = getHeight() - (mSolo.diameter / 2);
                        yDelta *= -1;
                    }
                    repaint();
                }
            });
            timer.start();
        }
    }

    @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2 = (Graphics2D) g;
        mSolo.DrawSprite(g2);
    }

}

现在,您需要做的就是单击 "Start" 按钮时,调用 start 方法

public class ClientClass {

    private JFrame frame;

    /**
     * Launch the application.
     */
    public static void main(String[] args) {
        EventQueue.invokeLater(new Runnable() {
            public void run() {
                try {
                    ClientClass window = new ClientClass();
                    window.frame.setVisible(true);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * Create the application.
     */
    public ClientClass() {
        initialize();

    }

    /**
     * Initialize the contents of the frame.
     */
//  Logic Logical;
    private void initialize() {
        frame = new JFrame();
        frame.setBounds(100, 100, 590, 520);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        SpriteField panel = new SpriteField();

        panel.setForeground(Color.WHITE);
        panel.setBackground(Color.GRAY);
        frame.getContentPane().add(panel);

        JButton btnStart = new JButton("Start");
        btnStart.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                panel.start();
            }
        });
        frame.getContentPane().add(btnStart, BorderLayout.SOUTH);
    }
}