0

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

I have looked at the similar questions and although one question is similar, it isn't addressing my problem. I have a sprite in a JPanel and I am unable to get it to move. One of the requirements I have to meet for the program is that it must begin moving when a JButton is pressed (Mouse Click). I have the code set-up in a way I believe should work, but it will spit out a long list of errors when I press the button. I'm also required to have the panel be a custom panel class.

What I need to know is this:

  1. Methods (ha) of programming sprite movement.
  2. Continuing to move the sprite without a trail.
  3. Making the sprite bounce off the edges of the panel. Done (Unable to test due to no moving ball)

Here's the code I have (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);
}
}

And here are my other Classes (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);

}


}

If you do not understand my Java comments, you can just ignore them. If you need more details to help me, let me know.

EDIT 1: Andrew, the closest article I could find used arrow keys to move a sprite. The article was "Sprite not moving in JPanel". All the other articles I found either addressed JPanels without sprites, or animating a sprite. However, I need a JButton that is MouseClicked to simply start the movement, and the ball does not change shape or color. I believe I have the collision part working, but I'm unable to test it until the ball starts moving.

EDIT 2: LuxxMiner, Thanks for the hints. I have refined my collision portion to be a little more accurate using the getHeight and getWidth methods.

EDIT 3: MadProgrammer, Thanks...? The problem is not the painting of the ball, I cannot get the ball to move in the first place to repaint it. And the example uses arrow keys, not a mouse click or JButton.

P Gribble
  • 3
  • 4
  • 1
    *"I have looked at the similar questions and although one question is similar, it isn't addressing my problem."* Which were the top three closest matches, and why did they not fix the problem? BTW For better help sooner, post a [MCVE] or [Short, Self Contained, Correct Example](http://www.sscce.org/). – Andrew Thompson Aug 03 '15 at 21:28
  • 1
    What exactly are the errors? Btw, the questions seem pretty self-explaining to me, just create a game-loop that calls your methods. For the "bounce" you can just create a method that checks if `x` is for example `< 10` or `> yourPanel.getWidth() - 10` and changes direction if one of the conditions are true. (Same with `y,` there you have to use `yourPanel.getHeight()`, ofc) I didn't try to copy your classes into my IDE, but a MCVE would be very helpful anyways :) – Lukas Rotter Aug 03 '15 at 21:34
  • 1
    Start by having a look at [Painting in AWT and Swing](http://www.oracle.com/technetwork/java/painting-140037.html) and [Performing Custom Painting](http://docs.oracle.com/javase/tutorial/uiswing/painting/). In order for a component to be painted, it must be added to something that is visible on the screen. When Swing wants you to update the state of the component, it calls your `paintComponent` indirectly. One thing you need to understand with Swing is YOU DO NOT CONTROL PAINTING; Swing will tell you when it needs things updated. You can make requests for updates using `repaint` – MadProgrammer Aug 03 '15 at 21:41
  • You could take a look at [this example](http://stackoverflow.com/questions/31776736/why-does-the-thread-freeze-my-code-in-my-actionlistener-implementation/31777440#31777440) which I believe is the same assignment – MadProgrammer Aug 03 '15 at 21:42
  • *"EDIT 3: MadProgrammer, Thanks...? The problem is not the painting of the ball, I cannot get the ball to move in the first place to repaint it. And the example uses arrow keys, not a mouse click or JButton."* They are related, but you need to look pass your trees to see the forest – MadProgrammer Aug 03 '15 at 23:22

1 Answers1

1

First, take a look at Painting in AWT and Swing and Performing Custom Painting to understand how painting works in Swing.

Let's have a look at the code...

You have a Ball class, which has it's own properties, but then your DrawSprite method passes in values which override these properties?

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

    }

}

What's the point of that? The Ball should paint it's own current state. You should get rid of the additional parameters

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

    }

}

ClientClass, Logic and SpriteField all have their own Ball references, none of which is shared so if Logic where to update the state of it's Ball, neither ClientClass or SpriteField would actually see those changes.

In reality, only SpriteField needs an instance of Ball, as it's basically the "ball container", it has the information need to determine if the ball moves out of bounds and wants to know when the ball should be repainted, better to isolate the functionality/responsibility for the Ball to SpriteField at this time.

You also need a means to actually move the ball. While you could use other events, I'd be nice if the ball just moved itself, to this end, you can use a Swing Timer, which won't block the Event Dispatching Thread, but which notifies the registered ActionListener within the context of the EDT, making it safe to update the UI from within.

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

}

Now, all you need to do, is when the "Start" button is clicked, call the start method

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);
    }
}
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • I'm sorry if I sounded rude. I am a student and am still learning how to program more complex things. I probably don't know most of the functions, classes, methods, etc. that most professionals use often. As it is, your answer has helped me complete the program. Sometimes my professor sometimes does not seem teach everything we need, and I answered you before I really understood what you were telling me. We have not gone over the visual aspects of Java much. Anyways, thank you for your help! It has helped me finish my program! :D – P Gribble Aug 04 '15 at 01:12
  • Glad it could help ;) - Don't forget though, not every answer will be a duplicate to your question, sometimes you need to pick and choose aspects of answers which are "close" to what you are struggling with and see what you can make of it. – MadProgrammer Aug 04 '15 at 01:31