How can I scroll more than one object at the same time?
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);
}
}
Solution 1:
"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);
}
});
}
}