I am currently working on a 2D shooter with moving background. The background is just a simple image. I want to implement that the image is moving endless from top to bottom.
I thought about determining the part of the image that is at the bottom outside the visible part of the frame and paint it on the top again.
@Override
public void paint(Graphics go) {
Graphics2D g = (Graphics2D) go;
g.fillRect(0, 0, this.getWidth(), this.getHeight());
g.drawImage(this.imgBackground, 0, this.yBackground, this);
}
yBackground is the coordinate where the upper left corner of the image is drawn. It is altered from another thread:
while (!this.paused) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
int y = this.display.getBackgroundY();
if (y == this.display.getHeight()) {
y = 0;
} else {
y++;
}
this.display.setBackgroundY(y);
}
So here is my question: how can I get the part of the image this is outside the visible part of the frame?
There are a number of ways you might be able to achieve this, but the basic premise is, you need some kind of scroll offset that determines the start of the basic image.
You then need to fill in the area before and after it (in case the image is smaller then the available height) until the space is filled.
The following example uses a javax.swing.Timer
to update a offset by a given amount. The paintComponent
method then renders all the space before and after it, including the current image position.
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class ScrollingBackground {
public static void main(String[] args) {
new ScrollingBackground();
}
public ScrollingBackground() {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setLayout(new BorderLayout());
frame.add(new BackgroundPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class BackgroundPane extends JPanel {
private BufferedImage bg;
private int yOffset = 0;
private int yDelta = 4;
public BackgroundPane() {
try {
bg = ImageIO.read(new File("Background.png"));
} catch (IOException ex) {
ex.printStackTrace();
}
Timer timer = new Timer(40, new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
yOffset += yDelta;
if (yOffset > getHeight()) {
yOffset = 0;
}
repaint();;
}
});
timer.start();
}
@Override
public Dimension getPreferredSize() {
return bg == null ? new Dimension(200, 200) : new Dimension(bg.getWidth(), bg.getHeight());
}
@Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (bg != null) {
Graphics2D g2d = (Graphics2D) g.create();
int xPos = (getWidth() - bg.getWidth()) / 2;
int yPos = yOffset;
while (yPos > 0) {
yPos -= bg.getHeight();
g2d.drawImage(bg, xPos, yPos, this);
}
yPos = yOffset;
while (yPos < getHeight()) {
g2d.drawImage(bg, xPos, yPos, this);
yPos += bg.getHeight();
}
g2d.dispose();
}
}
}
}
You might be able to optimism this by using a backing buffer and or subImage, but you get the idea...
You can just draw the image again starting at height yBackground + imgBackground.getHeight()
. The extra extending above the view gets clipped off, just like the bottom is clipped off in your code already.