Reputation: 21
I am trying to create a simple Java 2D physics game, and the current problem that I am running into is that I cannot think of a way to center the camera on the spaceship as it moves around.
I have the model of the ship as a component in the panel Universe
which is in the frame main. Currently the panel displays the pixels from (0, 0)
to (1000, 1000)
.
What I want to happen, is if the ship moves 10 pixels to the right, then the panel will display pixels from (10, 0)
to (1010, 1000)
, following the ship, but remaining rigid in the frame.
Or maybe there is a better way to achieve a similar effect.
This is the relevant part I believe:
public class Main extends JFrame {
public static void main(String[] args) {
M = new Main(1000, 1000);
}
public Main(int width, int height) {
boardWidth = width;
boardHeight = height;
FRAMERATE = 60;
setSize(boardWidth, boardHeight);
setTitle("space");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setVisible(true);
createUniverse();
createShip();
runThreads();
}
private void createUniverse() {
U = new Universe();
U.setLayout(new BorderLayout());
add(U, BorderLayout.CENTER);
}
private void createShip() {
U.createShip();
}
private void runThreads() {
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(5);
long lengthOfFrame = (long) (1000 / FRAMERATE);
executor.scheduleAtFixedRate(new Input(U.getShip(), FRAMERATE), 0L, lengthOfFrame, TimeUnit.MILLISECONDS);
executor.scheduleAtFixedRate(new UpdatePhysics(U.getObjects(), FRAMERATE), 0L, lengthOfFrame, TimeUnit.MILLISECONDS);
executor.scheduleAtFixedRate(new RepaintFrame(this), 0L, lengthOfFrame, TimeUnit.MILLISECONDS);
this.addKeyListener(new UserInput());
}
}
That is the JFrame
that holds this panel:
public class Universe extends JPanel {
public Universe() {
setSize(Main.boardWidth, Main.boardHeight);
setBackground(Color.BLACK);
setVisible(true);
}
public void createShip() {
ship = new Ship(new double[]{getWidth() / 2, getHeight() / 2});
shipM = new ShipModel(ship);
_objects = new MassiveObject[1];
_objects[0] = ship;
add(shipM, BorderLayout.CENTER);
}
}
The ShipModel
is just a Java component that is represented by a few polygons.
The frame is painted by the thread in Main
which basically just calls Main.repaint()
repeatedly.
I think thats all the relevant bits. My bad for the link, never posted before.
Upvotes: 0
Views: 191
Reputation: 285405
Consider placing your background image within a JScrollPane, remove the scrollbars by setting the appropriate scrolling policy:
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
You can move the image held within the JScrollPane by calling
backgroundLabel.scrollRectToVisible(rect);
and change the location of this rect based on button presses.
For example:
import java.awt.Dimension;
import java.awt.Graphics2D;
import java.awt.GridBagLayout;
import java.awt.Rectangle;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.geom.AffineTransform;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import javax.imageio.ImageIO;
import javax.swing.*;
@SuppressWarnings("serial")
public class SideScroll extends JLayeredPane {
public static final String BG_IMG_PATH = "https://upload.wikimedia.org/wikipedia/commons"
+ "/a/ad/Tomada_da_cidade_de_S%C3%A3o_Salvador_s%C3%A9culo_XVIII_%28panor%C3%A2mico%29.jpg";
public static final String CAMEL_PATH = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/33/PEO-bactrian_camel.svg/200px-PEO-bactrian_camel.svg.png";
private static final int PREF_W = 800;
private static final int PREF_H = 650;
protected static final int SCALE = 10;
private JLabel backgroundLabel = new JLabel();
JScrollPane scrollPane = new JScrollPane(backgroundLabel);
private JLabel camelLabel = new JLabel();
public SideScroll(Icon bgIcon, Icon camelIcon) {
camelLabel.setIcon(camelIcon);
camelLabel.setSize(camelLabel.getPreferredSize());
JPanel camelPanel = new JPanel(new GridBagLayout());
camelPanel.setOpaque(false);
camelPanel.add(camelLabel);
camelPanel.setSize(getPreferredSize());
backgroundLabel.setIcon(bgIcon);
scrollPane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
scrollPane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
scrollPane.setSize(getPreferredSize());
add(scrollPane, JLayeredPane.DEFAULT_LAYER);
add(camelPanel, JLayeredPane.PALETTE_LAYER);
setFocusable(true);
addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
int keyCode = e.getKeyCode();
switch (keyCode) {
case KeyEvent.VK_LEFT:
moveImg(-1, 0);
break;
case KeyEvent.VK_RIGHT:
moveImg(1, 0);
break;
case KeyEvent.VK_UP:
moveImg(0, -1);
break;
case KeyEvent.VK_DOWN:
moveImg(0, 1);
break;
default:
break;
}
}
private void moveImg(int right, int down) {
Rectangle rect = backgroundLabel.getVisibleRect();
int x = rect.x + SCALE * right;
int y = rect.y + SCALE * down;
int width = rect.width;
int height = rect.height;
rect = new Rectangle(x, y, width, height);
backgroundLabel.scrollRectToVisible(rect);
}
});
}
@Override
public Dimension getPreferredSize() {
if (isPreferredSizeSet()) {
return super.getPreferredSize();
}
return new Dimension(PREF_W, PREF_H);
}
private static void createAndShowGui() {
Icon bgIcon = null;
BufferedImage camel = null;
Icon camelIcon = null;
BufferedImage bgImg;
try {
URL bgImageUrl = new URL(BG_IMG_PATH);
URL camelUrl = new URL(CAMEL_PATH);
bgImg = ImageIO.read(bgImageUrl);
camel = ImageIO.read(camelUrl);
// make background one quarter the size because it's too big
int imgW = bgImg.getWidth() / 4;
int imgH = bgImg.getHeight() / 4;
BufferedImage bgImage2 = new BufferedImage(imgW, imgH, BufferedImage.TYPE_INT_ARGB);
Graphics2D g2 = bgImage2.createGraphics();
g2.drawImage(bgImg, 0, 0, imgW, imgH, null);
g2.dispose();
bgIcon = new ImageIcon(bgImage2);
// flip camel image so facing right
imgW = camel.getWidth();
imgH = camel.getHeight();
BufferedImage camelImg = new BufferedImage(imgW, imgH, BufferedImage.TYPE_INT_ARGB);
g2 = camelImg.createGraphics();
AffineTransform xform = AffineTransform.getTranslateInstance(imgW, 0);
xform.scale(-1, 1);
g2.drawImage(camel, xform, null);
g2.dispose();
camelIcon = new ImageIcon(camelImg);
} catch (IOException e) {
e.printStackTrace();
System.exit(-1);
}
JFrame frame = new JFrame("SideScroll");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(new SideScroll(bgIcon, camelIcon));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> createAndShowGui());
}
}
Upvotes: 0
Reputation: 406
My suggestion would be to go a different route, or at least a slightly different route.
You definitely do not want to retrieve pixels from the picture and draw the new pixels every frame (you will draw the pixels every frame, but getting pixels every tick will slow you down)
You could load the background image altogether (which you are likely already doing from your abstract description) and then move that background image's position according to button presses. The ship would always be rendered in the middle. (The edges of the background image is where this gets tricky -- a whole other challenge to tackle)
There is also the option of splitting the background image into tiles and moving those accordingly.
I've done both of the above, and since I wanted the background image to be extremely large, it only moved smoothly after I split it up into tiles programmatically, storing it in memory and rending each tile according to the position.
Like I said, the edges are where it gets tricky if you want your spaceship to move to the edge. If you just show infinite black behind the background image and you don't mind showing that, then you can just keep the same physics and have only half of the screen showing the background image.
So summary, just move the background image within the frame/panel accordingly.
Your question was not super clear and linking to your code is not great practice on Stack Overflow (it's better to include the relevant code snippets in your post using backticks like this:
This is code, I used backticks to surround it
That makes it scroll if it's long.
Upvotes: 1