stackoverflow community!
I am making a small GUI based game in JAVA.
I am having a few troubles right now.
To give you basic understanding of my program,
I have a window that shows menu(JPanel) first. When I click on "start game" button. It proceeds to another JPanel on which I can play game.
![menu_window][1]
I have a timer working on a small bullet that is moving periodically. Every time the timer works, the Panel repaints the bullet. I can see the repaint method works but it doesn't remove the previous trace.
![enter image description here][2]
This is my main class
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class Main {
private final int window_x_size = 500;
private final int window_y_size = 500;
private JFrame frame;
private JPanel Menu;
private Game myGame = new Game();//extends from JPanel
private JButton startButton = new JButton("Game Start");
private JButton exitButton = new JButton("Game Exit");
private JButton showRank = new JButton("Rank");
private JButton OneOnOne = new JButton("One on One");
private ActionListener MyButtonListener = new MyButtonListener();
public class MyButtonListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent e) {
Object source = e.getSource();
if (source == startButton) {
frame.remove(Menu);
frame.setContentPane(myGame);
frame.validate();
frame.repaint(); // prefer to write this always.
} else if (source == exitButton) {
System.exit(1);
} else if (source == showRank) {
} else {// one on one
}
}
}
public Main() {
frame = new JFrame();
frame.setBackground(Color.white);
frame.setSize(window_x_size, window_y_size);
frame.setTitle("My First GUI Game");
frame.setDefaultCloseOperation(frame.EXIT_ON_CLOSE);
Menu = new JPanel();
startButton.addActionListener(MyButtonListener);
exitButton.addActionListener(MyButtonListener);
showRank.addActionListener(MyButtonListener);
OneOnOne.addActionListener(MyButtonListener);
Menu.setLayout(new GridLayout(4, 1));
Menu.add(startButton);
Menu.add(OneOnOne);
Menu.add(showRank);
Menu.add(exitButton);
frame.setContentPane(Menu);
frame.setVisible(true);
}
public static void main(String[] args) {
/*
* This is the most important part of your GUI app, never forget
* to schedule a job for your event dispatcher thread :
* by calling the function, method or constructor, responsible
* for creating and displaying your GUI.
*/
SwingUtilities.invokeLater(new Runnable()
{
#Override
public void run()
{
new Main();
}
});
}
}
This is my Game class
import java.awt.Graphics;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.util.Random;
import javax.swing.ImageIcon;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Game extends JPanel {
private Random rnd = new Random();
private ActionListener MyBulletListener = new BulletListener();
public KeyListener myKeyListen = new MyKeyListener();
Timer bullet_timer;
private boolean IsExplosion = false;
private ImageIcon spacecraftImg = new ImageIcon("spacecraft.png");
private Rectangle spacecraftBox = new Rectangle(5, 10,
spacecraftImg.getIconWidth(), spacecraftImg.getIconHeight());
private ImageIcon explosionImg = new ImageIcon("explosion.png");
private ImageIcon bulletImg = new ImageIcon("bullet.png");
private Rectangle bulletBox = new Rectangle(70, 200,
bulletImg.getIconWidth(), bulletImg.getIconHeight());
private MySound explosion_sound = new MySound();
public Game() {
addKeyListener(myKeyListen);
bullet_timer = new Timer(500, MyBulletListener);
bullet_timer.start();
}
public class MyKeyListener implements KeyListener {
private int x;
private int y;
#Override
public void keyPressed(KeyEvent e) {
// TODO Auto-generated method stub
int keyCode = e.getKeyCode();
if (keyCode == 37) {
x = -10;
y = 0;
} else if (keyCode == 38) {
x = 0;
y = -10;
} else if (keyCode == 39) {
x = 10;
y = 0;
} else {
x = 0;
y = 10;
}
if (spacecraftBox.x + x < 0
|| spacecraftBox.x + x > 500
|| spacecraftBox.y + y < 0
|| spacecraftBox.y + y > 500) {
} else {
move_plane(x, y);
repaint();
}
}
#Override
public void keyReleased(KeyEvent e) {
// TODO Auto-generated method stub
}
#Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
}
public class BulletListener implements ActionListener {
private int x;
private int y;
#Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
Object source = e.getSource();
if (source == bullet_timer) {
int dir = rnd.nextInt(4);
if (dir == 0) {// move left
x = -10;
y = 0;
} else if (dir == 1) {// move right
x = 10;
y = 0;
} else if (dir == 2) {// move up
x = 0;
y = -10;
} else {// move down
x = 0;
y = 10;
}
if (bulletBox.x + x < 0
|| bulletBox.x + x > 500
|| bulletBox.y + y < 0
|| bulletBox.y + y > 500) {
} else {
move_bullets(x, y);
repaint();
}
}
}
}
#Override
public void paint(Graphics g) {
g.drawImage(bulletImg.getImage(), (int) bulletBox.getX(),
(int) bulletBox.getY(), null);
g.drawImage(spacecraftImg.getImage(), (int) spacecraftBox.getX(),
(int) spacecraftBox.getY(), null);
if (IsExplosion) {
g.drawImage(explosionImg.getImage(), (int) bulletBox.getX(),
(int) bulletBox.getY(), null);
MySound.play();
IsExplosion = false;
}
}
public void move_plane(int x, int y) {
spacecraftBox.translate(x, y);
if (spacecraftBox.intersects(bulletBox)) {
IsExplosion = true;
}
}
public void move_bullets(int x, int y) {
bulletBox.translate(x, y);
if (spacecraftBox.intersects(bulletBox)) {
IsExplosion = true;
}
}
}
Plus I added KeyListener but it doesn't work. I have no idea how to fix it out.
I tried googling about the issue. I tried the game panel focusable in main class but it didn't work.
I would really appreciate your help.
Best Regards,
Dongseop
This is almost always due to your painting method not calling the super's painting method to clean up dirty pixels. Since you're overriding paint, you would need to call super.paint(g); as the first method call in your paint override.
Other issues:
Next I'm going to suggest that you not override paint but rather paintComponent(Graphics g), as this will give your animation automatic double buffering and make the animation smoother. Again, call super.paintComponent(g); in your method override.
The easiest way to swap views is to use a CardLayout.
Better to use KeyBindings rather than a KeyListener as this will help you get around the KeyListener's focus issue, the issue which is probably preventing your KeyListener from working. It also allows you to use reusable Actions. Please search this site on KeyListeners to see what I mean and to see examples of Key Bindings (some by me). Also Google Java Key Bindings Tutorial to see the official tutorial on how to use these.
Related
I'm trying to make a demo spaceship shooter. Every time i press the space bar I draw a new projectile(Image) in the paintComponent(Graphics g) method and call a moveProjectile() method. The problem is the moveProjectile() method seems to be off.
MyJPanel.java
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionListener;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class MyJPanel extends JPanel implements ActionListener
{
private static final long serialVersionUID = 1L;
private Timer timer;
private Image backgroundImage;
private Image player;
private int playerX, playerY;
private int projectileX,projectileY;
private Image projectileImage;
private ArrayList<Image> projectiles = new ArrayList<Image>();
boolean flag = false;
public MyJPanel(Image backgroundImage, Image player,Image projectileImage)
{
this.backgroundImage = backgroundImage;
this.player = player;
this.projectileImage = projectileImage;
this.setLayout(null);
timer = new Timer(50, this);
timer.start();
this.addKeyListener(new KeyAdapter() // Listens for a keyboard event
{
public void keyPressed(KeyEvent e)
{
if (e.getKeyCode() == KeyEvent.VK_SPACE) // If pressing space - shoot
{
flag = true;
moveProjectile();
}
repaint();
}
});
// Mouse listener
this.addMouseMotionListener(new MouseMotionListener()
{
#Override
public void mouseMoved(MouseEvent e)
{
playerX = e.getX();
playerY = e.getY();
}
#Override
public void mouseDragged(MouseEvent e)
{
}
});
hideMouseCursor();
this.setFocusable(true);
this.setVisible(true);
} // End of JPanle constructor
public void paintComponent(Graphics graphics)
{
super.paintComponent(graphics);
graphics.drawImage(backgroundImage,0,0,this.getWidth(),this.getHeight(),null); // Draw the background
graphics.drawImage(player,playerX,playerY,null); // Draw the player
if (flag)
{
projectileX = playerX + player.getWidth(null);
projectileY = playerY + player.getHeight(null) / 2 - 27;
graphics.drawImage(projectileImage,projectileX,projectileY,null);
}
}
public void moveProjectile()
{
while (projectileX < this.getWidth())
{
this.projectileX += 2;
repaint();
}
}
public void hideMouseCursor() // Hides the mouse cursor
{
//Transparent 16 x 16 pixel cursor image.
BufferedImage cursorbackgroundImgage = new BufferedImage(16, 16, BufferedImage.TYPE_INT_ARGB);
// Create a new blank cursor.
Cursor blankCursor = Toolkit.getDefaultToolkit().createCustomCursor(
cursorbackgroundImgage, new Point(0, 0), "Blank Cursor");
// Set the blank cursor to the JPanel.
this.setCursor(blankCursor);
}
#Override
public void actionPerformed(ActionEvent actionEvent) // Without the method and the repaint() the mouse listener will not work
{
repaint();
}
public class ProjectileThread extends Thread
{
#Override
public void run()
{
projectileX = playerX + player.getWidth(null);
projectileY = playerY + player.getHeight(null) / 2;
}
}
public static void main(String[] args)
{
JFrame frame = new JFrame("A Game by me");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setExtendedState(JFrame.MAXIMIZED_BOTH); // Making the frame take a full screen
ImageIcon backgroundImageIcon = new ImageIcon("space_background_2.jpg");
Image backgroundImgage = backgroundImageIcon.getImage();
ImageIcon playerImageIcon = new ImageIcon("spaceship_1.png");
Image playerImage = playerImageIcon.getImage();
ImageIcon projectileIcon = new ImageIcon("spaceship_projectile_1.png");
Image projectileImage = projectileIcon.getImage();
frame.add(new MyJPanel(backgroundImgage,playerImage,projectileImage));
frame.setVisible(true);
}
} // End of MyJPanel
There some variables and methods i don't use so don't mind them please. The points to notice in the code:
if (e.getKeyCode() == KeyEvent.VK_SPACE) // If pressing space - shoot
{
flag = true;
moveProjectile();
}
repaint();
If pressing space -> flag = true which means
if (flag)
{
projectileX = playerX + player.getWidth(null);
projectileY = playerY + player.getHeight(null) / 2 - 27;
graphics.drawImage(projectileImage,projectileX,projectileY,null);
}
Draw the projectile and move it to the right. The problem is it's never moved to the right. Instead it's following the spaceship which moves by the mouse.
Any suggestions will be very appreciated.
public class ProjectileThread extends Thread
{
public ProjectileThread(int playerX,int playerY)
{
projectileX = playerX + player.getWidth(null);
projectileY = playerY + player.getHeight(null) / 2;
}
#Override
public void run()
{
while (projectileX < getWidth())
{
projectileX += 2;
}
}
}
public void keyPressed(KeyEvent e)
{
if (e.getKeyCode() == KeyEvent.VK_SPACE) // If pressing space - shoot
{
ProjectileThread projectileThread = new ProjectileThread(playerX,playerY);
projectileThread.start();
}
repaint();
}
Your moveProjectile method moves the projectile horizontally by adding 2 to its X coordinate
this.projectileX += 2;
However, your paint method overwrite this value with your player X coordinate:
projectileX = playerX + player.getWidth(null);
So everytime you paint your projectile, it's in the same place relative to the player. You need to draw the projectile first using the player coordinates, and then move it without resetting its X coordinate.
You have a thread that should be started when the projectile is fired. So in your handler for the space key, start the thread and pass it the player coordinate. This is your start point. Then in the run method of the thread, move the projectile and wait a bit in a loop.
while (projectileX < getWidth())
{
projectileX += 2;
}
All this does is set projectile = getWidth() + 1 (or possibly+2).
Similarly, your moveProjectile method has this:
while (projectileX < this.getWidth())
{
this.projectileX += 2;
repaint();
}
Which does the same thing. Note that the repaint() in the body of the loop essentially does nothing, since you block the event thread until the moveProjectile() method returns
Although there are questions similar, I think mine is slightly different because of how I have my code set up. I have a JFrame within my main method. However, I only have JPanel in my constructor. I tried to make some of my variables static so that I could access them in the main method and say, for instance, if the x-coordinate of this graphic plus its width is greater than frame.getWidth().. but that won't work for some reason. I don't want to bombard anyone with code so I will just try to put the main information and if you need more, I'll update it.
package finalProj;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Rectangle;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.geom.Ellipse2D;
public class nonaMaingamePractice extends JPanel implements ActionListener, KeyListener {
/**
*
*/
private static final long serialVersionUID = 1L;
private static Ellipse2D ellipse;
static Toolkit tools = Toolkit.getDefaultToolkit();
static int screenWidth = (int)(Math.round(tools.getScreenSize().getWidth()));
static int screenHeight = (int)(Math.round(tools.getScreenSize().getHeight()));
private static Rectangle paddleRect;
JLabel text = new JLabel("cool");
Timer timeMove = new Timer(1, this);
Timer timeBall = new Timer(10, new timeBall());
private static double x = screenWidth/2, y = (screenHeight*0.8), xx = 0, yy = 0, score = 0, Ox = screenWidth/2, Oy = screenHeight/2, Oyy = 0, width = 100, height = 30;
public nonaMaingamePractice(){
setLayout(new BorderLayout());
timeBall.start();
timeMove.start();
addKeyListener(this);
setFocusable(true);
JPanel panelNorth = makePanel();
panelNorth.setBackground(Color.CYAN);
add(panelNorth, BorderLayout.NORTH);
JLabel scoreLabel = new JLabel("Score: " + score);
panelNorth.add(scoreLabel);
}
public void paintComponent(Graphics g){
super.paintComponent(g);
g.setColor(Color.BLUE);
paddleRect = new Rectangle((int)x, (int)y, (int)width, (int)height);
ellipse = new Ellipse2D.Double(Ox, Oy+Oyy, 50, 50);
Graphics2D graphics = (Graphics2D)g;
graphics.fill(paddleRect);
graphics.fill(ellipse);
}
#Override
public void actionPerformed(ActionEvent e) {
x = x + xx;
y = y + yy;
if(x<0){
x=0;
xx=0;
}
repaint();
}
#Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
#Override
public void keyPressed(KeyEvent e) {
int c = e.getKeyCode();
if(c==KeyEvent.VK_RIGHT){
xx=1;
}else if(c==KeyEvent.VK_LEFT){
xx=-1;
}
}
#Override
public void keyReleased(KeyEvent e) {
xx=0;
}
protected JPanel makePanel() {
#SuppressWarnings("serial")
JPanel pane = new JPanel() {
#Override
public Dimension getPreferredSize() {
return new Dimension(100, 30);
}
};
pane.setBackground(Color.CYAN);
return pane;
}
protected class timeBall implements ActionListener{
Timer timeWhateva = new Timer(100, this);
#Override
public void actionPerformed(ActionEvent e) {
try{
System.out.println(paddleRect.getX());
if(ellipse.intersects(paddleRect)){
timeWhateva.start();
Oy+=-1;
System.out.println(ellipse.getX() + " " + ellipse.getY());
}else if(!ellipse.intersects(paddleRect)){
Oyy+=1;
}
}catch(RuntimeException NullPointerException){
System.out.println(NullPointerException.getMessage());
}
repaint();
}
}
public static void main(String[] args){
nonaMaingamePractice main = new nonaMaingamePractice();
JFrame frame = new JFrame();
frame.add(main);
frame.setVisible(true);
frame.setTitle("Project 4 game");
frame.setSize(screenWidth, screenHeight);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
Okay, so there seems to a few things that are wrong.
First, don't rely on static for cross object communication, this is a really bad idea which will come back to bite you hard. Instead, pass information to the classes which need it.
Second, I'd focus on having a single Timer (or "main-loop") which is responsible for updating the current state of the game and scheduling repaints. This is the basic concept of Model-View-Controller paradigm
The first thing I'm going to do is take your code apart completely and rebuild it...
To start with, I want some kind of interface which provides information about the current state of the game and which I can pass instances of to other parts of the game in order for them to make decisions and update the state of the game...
public interface GameView {
public boolean isKeyRightPressed();
public boolean isKeyLeftPressed();
public Dimension getSize();
public void updateState();
}
This provides information about the state of the right and left keys, the size of the view and provides some basic functionality to request that the view update it's current state
Next, we need some way to model the state of the game...
import java.awt.Rectangle;
import java.awt.geom.Ellipse2D;
public interface GameModel {
public Rectangle getPaddle();
public Ellipse2D getBall();
public void ballWasMissed();
}
So, this basically maintains information about the paddle and ball and provides a means by which the "main game loop" can provide notification about the state of the game back to the model
Next, we need to the actual "main game loop" or controller. This is responsible for updating the state of the model and updating the view...
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
public class MainLoop implements ActionListener {
private GameView gameView;
private GameModel gameModel;
private int ballYDelta = 1;
public MainLoop(GameView gameView, GameModel gameModel) {
this.gameView = gameView;
this.gameModel = gameModel;
}
#Override
public void actionPerformed(ActionEvent e) {
Rectangle paddle = gameModel.getPaddle();
Ellipse2D ball = gameModel.getBall();
// Update the paddle position...
if (gameView.isKeyLeftPressed()) {
paddle.x--;
} else if (gameView.isKeyRightPressed()) {
paddle.x++;
}
// Correct for overflow...
if (paddle.x < 0) {
paddle.x = 0;
} else if (paddle.x + paddle.width > gameView.getSize().width) {
paddle.x = gameView.getSize().width - paddle.width;
}
// Update the ball position...
Rectangle bounds = ball.getBounds();
bounds.y += ballYDelta;
if (bounds.y < 0) {
bounds.y = 0;
ballYDelta *= -1;
} else if (bounds.y > gameView.getSize().height) {
// Ball is out of bounds...
// Notify the gameView so it knows what to do when the ball goes
// out of the game view's viewable, ie update the score...
// Reset ball position to just out side the top of the view...
gameModel.ballWasMissed();
bounds.y = -bounds.height;
} else if (paddle.intersects(bounds)) {
// Put the ball to the top of the paddle
bounds.y = paddle.y - bounds.height;
// Bounce
ballYDelta *= -1;
}
ball.setFrame(bounds);
// Update the view
gameView.updateState();
}
}
This is basically where we are making decisions about the current position of the objects and updating their positions. Here we check for "out-of-bounds" positions and update their states appropriately (for example, the ball can "bounce" and change directions)
The delta values are quite small, so you might want to play around with those
And finally, we need something that pulls it all together...
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.Timer;
public class NonaMaingamePractice extends JPanel implements KeyListener, GameView {
/**
*
*/
private static final long serialVersionUID = 1L;
JLabel text = new JLabel("cool");
private Timer timeBall;
private GameModel model;
private boolean init = false;
private boolean rightIsPressed;
private boolean leftIsPressed;
public NonaMaingamePractice() {
setLayout(new BorderLayout());
addKeyListener(this);
setFocusable(true);
JPanel panelNorth = makePanel();
panelNorth.setBackground(Color.CYAN);
add(panelNorth, BorderLayout.NORTH);
JLabel scoreLabel = new JLabel("Score: " + 0);
panelNorth.add(scoreLabel);
addComponentListener(new ComponentAdapter() {
#Override
public void componentResized(ComponentEvent e) {
if (getWidth() > 0 && getHeight() > 0 && !init) {
init = true;
model = new DefaultGameModel(getSize());
timeBall = new Timer(40, new MainLoop(NonaMaingamePractice.this, model));
timeBall.start();
} else if (model != null) {
model.getPaddle().y = (getHeight() - model.getPaddle().height) - 10;
}
}
});
}
#Override
public Dimension getPreferredSize() {
return new Dimension(200, 200);
}
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(Color.BLUE);
Graphics2D graphics = (Graphics2D) g;
if (model != null) {
graphics.fill(model.getPaddle());
graphics.fill(model.getBall());
}
}
#Override
public void keyTyped(KeyEvent e) {
// TODO Auto-generated method stub
}
#Override
public void keyPressed(KeyEvent e) {
int c = e.getKeyCode();
if (c == KeyEvent.VK_RIGHT) {
rightIsPressed = true;
} else if (c == KeyEvent.VK_LEFT) {
leftIsPressed = true;
}
}
#Override
public void keyReleased(KeyEvent e) {
int c = e.getKeyCode();
if (c == KeyEvent.VK_RIGHT) {
rightIsPressed = false;
} else if (c == KeyEvent.VK_LEFT) {
leftIsPressed = false;
}
}
protected JPanel makePanel() {
#SuppressWarnings("serial")
JPanel pane = new JPanel() {
#Override
public Dimension getPreferredSize() {
return new Dimension(100, 30);
}
};
pane.setBackground(Color.CYAN);
return pane;
}
#Override
public boolean isKeyRightPressed() {
return rightIsPressed;
}
#Override
public boolean isKeyLeftPressed() {
return leftIsPressed;
}
#Override
public void updateState() {
// Maybe update the score??
repaint();
}
public static void main(String[] args) {
NonaMaingamePractice main = new NonaMaingamePractice();
JFrame frame = new JFrame();
frame.add(main);
frame.setVisible(true);
frame.setTitle("Project 4 game");
frame.pack();
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}
i have been trying this code and editing it but it doesnt work.
it is suppose to get a keyboard input store it in the variable "c" and then compare that
to keys on the keyboard. Then it is suppose to move a little square depending the key pressed.
The keyboard doesnt seem to be recognised. Can someone please help me?
Please be aware i am 14 so please dont say how simple and easy it is, i am still learning and could you also write it in a way i am likely to understand. Thank you in advance
'package package1;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
public class Class1 extends JPanel implements KeyListener, ActionListener{
Timer timer = new Timer(5, this);
//variables
int cox = 0;
int spx = 0; //cox = coordinates x, spx = speedx
int coy = 0;
int spy = 0; //coy = coordinates y, spy = speedy
public void paintComponent(Graphics g)
{
timer.start();
super.paintComponent(g);
g.setColor(Color.BLUE);
g.fillRect(cox, coy, 50, 50);
addKeyListener(this);
setFocusable(true);
setFocusTraversalKeysEnabled(false);
}
public void actionPerformed(ActionEvent e)
{
cox = cox + spx;
coy = coy + spy;
repaint();
}
public void keyPressed(KeyEvent e)
{
int c = e.getKeyCode();
if(c == KeyEvent.VK_LEFT)
{
spx = -1;
spy = 0;
}
if(c == KeyEvent.VK_UP)
{
spx = 0;
spy = -1;
}
if(c == KeyEvent.VK_RIGHT)
{
spx = 1;
spy = 0;
}
if(c == KeyEvent.VK_DOWN)
{
spx = 0;
spy = 1;
}
}
public void keyTyped(KeyEvent e)
{
}
public void keyReleased(KeyEvent e)
{
spx = 0;
spy = 0;
}
public static void main(String args[])
{
Class1 t = new Class1();
JFrame frame = new JFrame("window");
frame.setSize(500,500);
frame.setVisible(true);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(t);
}
}
Problems with your code:
KeyListeners are very low-level constructs, and in general should be avoided in favor of key bindings in most Swing applications. Doing this will also make it much easier to solve focus issues that plague KeyListeners, and which is causing your current KeyListener to do nothing whatsoever. If you read most questions on this site tagged with Swing and KeyListeners, you'll see this advice time and again -- because it's true, and it works.
Please understand that the paintComponent(Graphics g) method is for painting only. It is frequently called, often out of your control, since the OS can induce it to be called.
The paintComponent method has a significant influence on the perceived responsiveness of your GUI, since it is in control of drawing your GUI and any animations that your GUI might contain. If it is slowed for any reason, your GUI will seem slow.
For this reason, this method should be used for drawing and only drawing and nothing but drawing. Specifically,
Don't start your Swing Timer from within this method,
Don't add KeyListeners from within this method (unless you want to add a KeyListener 20 times to the GUI which will result in wildly unpredictable behavior),
Don't change the state of any of your objects from within this method.
For example:
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.Map;
import javax.swing.*;
#SuppressWarnings("serial")
public class Class1B extends JPanel {
private static final int PREF_W = 500;
private static final int PREF_H = PREF_W;
private static final int ANIMATION_DELAY = 15;
private static final int RECT_WIDTH = 15;
private static final Color RECT_COLOR = Color.red;
private EnumMap<Direction, Boolean> dirMap = new EnumMap<>(Direction.class);
private Map<Integer, Direction> keyToDir = new HashMap<>();
private Timer animationTimer;
public int rectX;
public int rectY;
public Class1B() {
for (Direction dir : Direction.values()) {
dirMap.put(dir, Boolean.FALSE);
}
keyToDir.put(KeyEvent.VK_UP, Direction.UP);
keyToDir.put(KeyEvent.VK_DOWN, Direction.DOWN);
keyToDir.put(KeyEvent.VK_LEFT, Direction.LEFT);
keyToDir.put(KeyEvent.VK_RIGHT, Direction.RIGHT);
setKeyBindings();
animationTimer = new Timer(ANIMATION_DELAY, new AnimationListener());
animationTimer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(PREF_W, PREF_H);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.setColor(RECT_COLOR);
g.fillRect(rectX, rectY, RECT_WIDTH, RECT_WIDTH);
}
private void setKeyBindings() {
int condition = WHEN_IN_FOCUSED_WINDOW;
final InputMap inputMap = getInputMap(condition);
final ActionMap actionMap = getActionMap();
boolean[] keyPressed = { true, false };
for (Integer keyCode : keyToDir.keySet()) {
Direction dir = keyToDir.get(keyCode);
for (boolean onKeyPress : keyPressed) {
boolean onKeyRelease = !onKeyPress;
KeyStroke keyStroke = KeyStroke.getKeyStroke(keyCode, 0,
onKeyRelease);
Object key = keyStroke.toString();
inputMap.put(keyStroke, key);
actionMap.put(key, new KeyBindingsAction(dir, onKeyPress));
}
}
}
private class KeyBindingsAction extends AbstractAction {
private Direction dir;
boolean pressed;
public KeyBindingsAction(Direction dir, boolean pressed) {
this.dir = dir;
this.pressed = pressed;
}
#Override
public void actionPerformed(ActionEvent evt) {
dirMap.put(dir, pressed);
}
}
private class AnimationListener implements ActionListener {
#Override
public void actionPerformed(ActionEvent evt) {
boolean repaint = false;
for (Direction dir : Direction.values()) {
if (dirMap.get(dir)) {
rectX += dir.getIncrX();
rectY += dir.getIncrY();
repaint = true;
}
}
if (repaint) {
repaint();
}
}
}
private static void createAndShowGui() {
Class1B mainPanel = new Class1B();
JFrame frame = new JFrame("Class1B");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(mainPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui();
}
});
}
}
enum Direction {
UP(0, -1), DOWN(0, 1), LEFT(-1, 0), RIGHT(1, 0);
private int incrX;
private int incrY;
private Direction(int incrX, int incrY) {
this.incrX = incrX;
this.incrY = incrY;
}
public int getIncrX() {
return incrX;
}
public int getIncrY() {
return incrY;
}
}
I've been working on an "elevator simulator" where I have to animate "elevators". I was able to create different elevator objects, which I did by having each Elevator Object have the parameters of width, height, and coordinates. I then stored them all into an array and used a for loop to draw them into my frame using JPanel's PaintComponent method.
Can you guys suggest a way or give me advice to animate them separately from each other, like say move them up and down independently? I was able to make it move when I only had ONE elevator, but when I tried to apply it to multiple elevators, it did not work.
My previous attempt involved an ActionListener (that listens to a button press to "start" the animation) that simply changes the x and y coordinates of the SINGLE elevator. So How do I go and do that with several elevators (the number of elevators is arbitrary to the user). Do I have to make as many ActionListeners as there are elevators, so it can listen to them independently?
Here's the ActionListener that worked when there's only ONE elevator. It only moves up so far.
private ActionListener timerActionUp = new ActionListener()
{
private int iterator = 0;
private int top = 0;
public void actionPerformed(ActionEvent e)
{
if(top<floorQueue.length){
if(iterator<floorQueue[top]){ //floorQueue is so that the elevator knows where to stop
if(movefloorup<VERTICALFLOORDISTANCE*6){ //this is when the elevator reaches the "top" floor that can fit into the screen, and moves to the next column representing the floors further up
repaint();
movefloorup = movefloorup + VERTICALFLOORDISTANCE;
System.out.println(movefloorup);
iterator++;
}
else{
//timer.stop();
repaint();
switchmarker = 1; //makes elevator moves to the next column
movefloorup = 0;
iterator++;
}
}
else
{
System.out.println("Picking up passengers...");
elevatorCapacity = elevatorCapacity + peopleQueue[floorQueue[top]];
System.out.println("Passengers in elevator: "+elevatorCapacity);
peopleQueue[floorQueue[top]] = 0; //Updates the number of people in the elevator
top++;
if(elevatorCapacity >= 5)
{
System.out.println("WARNING! ELEVATOR FULL!");
elevfull = 1;
}
//timer.stop();
}
}
else
{
System.out.println("Done picking up passengers.");
timer.stop();
}
}
};
Thank you very much!
"Do I have to make as many ActionListeners as there are elevators, so it can listen to them independently?"
No, that would require multiple Timers. Avoid doing this whenever you can.
"Can you guys suggest a way or give me advice to animate them separately from each other, like say move them up and down independently?"
What you should do is try and implement the business logic in methods within your Elevator class and just call the those methods while looping through all the Elevators in your array.
Two make the Elevators to appear to move independently, you can have flags, say in your move method, like
public void move() {
if (move) {
// do something
}
}
What ever is your reason for making the elevator move, that will be the reason the raise the flag. And vice versa. Maybe something like if (onFloor) { elevator.move = false }, maybe for a duration of 20 timer "iterations", and keep a count in the elevator class, that will reset back to 0 when the count hits 20, then move will be back at true.
Here's an example you can play with. I was working on it for about 20 minutes then gave up. The logic is a bit off, but it basically points out the ideas i mentioned. Maybe you'll have better luck with it. You can also see a good working example of moving different object here
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class ElevatorAnimate extends JPanel {
private static final int D_W = 300;
private static final int FLOORS = 6;
private static final int FLOOR_HEIGHT = 100;
private static final int BUILDING_HEIGHT = FLOORS * FLOOR_HEIGHT;
private static final int D_H = BUILDING_HEIGHT;
private static final int BUILDING_BASE = BUILDING_HEIGHT;
private static final int ELEVATOR_HEIGHT = 60;
private static final int ELEVATOR_WIDTH = 30;
private final List<Elevator> elevators;
public ElevatorAnimate() {
elevators = createElevators();
Timer timer = new Timer(50, new ActionListener() {
public void actionPerformed(ActionEvent e) {
for (Elevator el : elevators) {
el.move();
}
repaint();
}
});
timer.start();
}
private List<Elevator> createElevators() {
List<Elevator> list = new ArrayList<>();
list.add(new Elevator(6, 30));
list.add(new Elevator(4, 90));
list.add(new Elevator(2, 150));
return list;
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
drawFloors(g);
for (Elevator el : elevators) {
el.drawElevator(g);
}
}
private void drawFloors(Graphics g) {
for (int i = 1; i <= FLOORS; i++) {
g.drawLine(0, FLOOR_HEIGHT * i, D_W, FLOOR_HEIGHT * i);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(D_W, D_H);
}
public class Elevator {
int floor, x, y;
boolean move = false;
boolean up = true;
int stopCount = 0;
public Elevator(int floor, int x) {
this.floor = floor;
y = BUILDING_HEIGHT - (floor * FLOOR_HEIGHT);
this.x = x;
}
public void drawElevator(Graphics g) {
g.fillRect(x, y, ELEVATOR_WIDTH, ELEVATOR_HEIGHT);
}
public void move() {
if (y <= 0) {
up = false;
} else if (y >= BUILDING_BASE + ELEVATOR_HEIGHT) {
up = true;
}
if (isOnFloor()) {
move = false;
}
if (move) {
if (up) {
y -= 2;
} else {
y += 2;
}
} else {
if (stopCount >= 20) {
move = true;
stopCount = 0;
} else {
stopCount++;
}
}
}
private boolean isOnFloor() {
return y / FLOOR_HEIGHT == 100;
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame();
frame.add(new ElevatorAnimate());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}
Starting from this related Subway simulation, the following variation adds two independent panels, each of which contains its own view and control panel.
// Common initialization for either JApplet or JFrame
private static void initContainer(Container container) {
container.add(createPanel(), BorderLayout.NORTH);
container.add(createPanel(), BorderLayout.SOUTH);
}
private static JPanel createPanel() {
JPanel panel = new JPanel(new BorderLayout());
ButtonPanel control = new ButtonPanel();
SubwayPanel subway = new SubwayPanel(control);
panel.add(subway, BorderLayout.NORTH);
panel.add(control, BorderLayout.SOUTH);
subway.beginOperation();
return panel;
}
New question was asked after this one, found here.
I'm new to Java, but I am working on a recreation of "Flappy Bird" to learn more about java and the way that graphics are displayed. Any solutions or suggestions to any of my questions is greatly appreciated. Thanks!
Right now, my program makes a random pipe and scrolls it, but I don't need it to keep scrolling when x1-3 = -83 (this is when the pipe will be off of the screen completely and is no longer needed).
Questions
How can I make my Game.class scroll more than one instance of Pipes.class while adding a preset distance between them? I could find out the distance to put between them, but as far as displaying more than one, I'm not sure how to do that. At most, 3 pipes have to be displayed at the same time.
How can I display a panel for the main menu, and then switch to the pipes panel after a start button is pressed?
Classes
Game.java
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class Game {
Pipes panel = new Pipes();
public Game() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(panel);
f.setTitle("Pipe Game");
f.setResizable(false);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
Timer timer = new Timer(10, new ActionListener() { //pipe speed
#Override
public void actionPerformed(ActionEvent e) {
panel.move();
}
});
timer.start();
Timer refresh = new Timer(30, new ActionListener() { //refresh rate
#Override
public void actionPerformed(ActionEvent e) {
panel.repaint();
}
});
refresh.start();
}
public static void main(String args[]) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
new Game();
}
});
}
}
Pipes.java
import java.awt.Dimension;
import java.awt.Graphics;
import javax.swing.JPanel;
public class Pipes extends JPanel {
//Declare and initialiaze variables
int x1 = 754; //xVal start
int x2 = 75; //pipe width
//total width is 83
int y1 = -1; //yVal start
int y2 = setHeightVal(); //pipe height
int gap = 130; //gap height
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.clearRect(0,0,750,500); //Clear screen
g.drawRect(x1,y1,x2,y2); //Draw part 1
g.drawRect(x1-3,y2-1,x2+6,25); //Draw part 2
g.drawRect(x1-3,y2+25+gap,x2+6,25); //Draw part 3
g.drawRect(x1,y2+25+gap+25,x2,500-y2-49-gap); //Draw part 4
}
public void move() {
x1--;
}
public int getMyX() { //To determine where the pipe is horizontally
return x1-3;
}
public int getMyY() { //To determine where the pipe is vertically
return y2+25;
}
public int setHeightVal() { //Get a random number and select a preset height
int num = (int)(9*Math.random() + 1);
int val = 0;
if (num == 9)
{
val = 295;
}
else if (num == 8)
{
val = 246;
}
else if (num == 7)
{
val = 216;
}
else if (num == 6)
{
val = 185;
}
else if (num == 5)
{
val = 156;
}
else if (num == 4)
{
val = 125;
}
else if (num == 3)
{
val = 96;
}
else if (num == 2)
{
val = 66;
}
else
{
val = 25;
}
return val;
}
#Override
public Dimension getPreferredSize() {
return new Dimension(751, 501);
}
}
"How can I make my Game.class scroll more than one instance of Pipes.class while adding a preset distance between them? "
Here's some simple logic. You want to use a data structure to hold you pipes. What this data structure will hold is whatever data is required to paint then, like x, y, coordinates. For this task, I prefer just to create a new class with it's own draw method, that I pass the paintComponent's Graphics context to. For example
public class Pipe {
int x;
int y;
public class Pipe(int x, int y) {
this.x = x;
this.y = y;
}
public void drawPipe(Graphics g) {
g.fillRect(x, y, 50, 100);
}
}
Now this is just an example class. The above only draws a rectangle, but this is just to show you what you should be doing.
So next you want to have the data structure to hold three Pipe objects, like an array. I prefer to use a List. You'll want that List in your Pipes class, and add three Pipe object to it. You can specify the x to be anything you like, to keep them the same distance apart
public class Pipes extends JPanel {
List<Pipe> pipes = new ArrayList<Pipe>();
public Pipes() {
pipes.add(new Pipe(50, 100));
pipes.add(new Pipe(150, 100));
pipes.add(new Pipe(250, 100));
}
}
Now in the paintComponent method, all you need to do is loop through them and use its drawPipe method
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for ( Pipe pipe : pipes ){
pipe.drawPipe(g);
}
}
Now you move them all you need to do is move there x positions in the timer, and call repaint. You may also want to check against the x to make sure it doesn't do off the screen, or if you moving them the right, you could put them the the very left then whey go off the screen, like a conveyor belt. So you could do something like this
private static final int X_INC = 5;
...
Timer timer = new Timer(40, new ActionListener(){
public void actionPerformed(ActionEvent e) {
for (Pipe pipe : pipes ){
if (pipe.x >= screenWidth) {
pipe.x = 0;
} else {
pipe.x += X_INC;
}
}
repaint();
}
});
As you can see, what I do is loop through the List and just change all their x coordinates, then repaint(). So you can create your own Pipe class with whatever values you need to paint, and just move them around in the loop.
For the changing of speed, instead of using a hard coded vakue like 10 for the timer, use a variable delay, that you can change like with the click of a button
int delay = 100;
JButton speedUp = new JButton("Speed UP");
JButton slowDown = new JButton("Slow Down");
Timer timer = null;
public Pipes() {
timer = new Timer(delay, new ActionListener(){
...
});
timer.start();
speedUp.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e) {
if (!((delay - 20) < 0)) {
delay -=20;
timer.setDelay(delay);
}
}
});
// do the same for slowDown, but decrease the delay
}
Test this out
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class Mario extends JPanel {
private static final int D_W = 800;
private static final int D_H = 300;
private static final int X_INC = 5;
BufferedImage bg;
BufferedImage pipeImg;
List<Pipe> pipes = new ArrayList<>();
int delay = 50;
Timer timer = null;
public Mario() {
try {
bg = ImageIO.read(new URL("http://farm8.staticflickr.com/7341/12338164043_0f68c73fe4_o.png"));
pipeImg = ImageIO.read(new URL("http://farm3.staticflickr.com/2882/12338452484_7c72da0929_o.png"));
} catch (IOException ex) {
Logger.getLogger(Mario.class.getName()).log(Level.SEVERE, null, ex);
}
pipes.add(new Pipe(100, 150, pipeImg));
pipes.add(new Pipe(400, 150, pipeImg));
pipes.add(new Pipe(700, 150, pipeImg));
timer = new Timer(delay, new ActionListener(){
public void actionPerformed(ActionEvent e) {
for (Pipe pipe : pipes) {
if (pipe.x > D_W) {
pipe.x = 0;
} else {
pipe.x += X_INC;
}
}
repaint();
}
});
timer.start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(bg, 0, 0, getWidth(), getHeight(), this);
for (Pipe pipe : pipes) {
pipe.drawPipe(g);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(D_W, D_H);
}
public class Pipe {
int x;
int y;
Image pipe;
public Pipe(int x, int y, Image pipe) {
this.x = x;
this.y = y;
this.pipe = pipe;
}
public void drawPipe(Graphics g) {
g.drawImage(pipe, x, y, 75, 150, Mario.this);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame("Mario Pipes");
frame.add(new Mario());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}