Im writing a River-Raid-Like game, and all images are drew with g.drawImage(...).
My question is: How to keep the content aspect ratio, and scale the content to fit the new window size ,when someone will resize the JFrame?
Is there any option like that? Can i do this without using JLabels and Layouts? If not, how to do this other way?
My code to draw things on the JPanel
private void doDrawing(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
drawStrings(g2);
mapa.drawMap(g2);
ArrayList ms = craft.getMissiles();
for (Object m1 : ms) {
Missile m = (Missile) m1;
g2.drawImage(m.getImage(), m.getX(), m.getY(), this);
}
g2.drawImage(craft.getImage(), craft.getX(), craft.getY(), this);
for (EnemyJet enemy : enemies) {
g2.drawImage(enemy.getImage(), enemy.getX(), enemy.getY(), this);
}
for (Fuel fuel : fuels) {
g2.drawImage(fuel.getImage(), fuel.getX(), fuel.getY(), fuel.getHeight(), fuel.getHeight(), this);
}
for (Obstacle o : obst) {
g2.drawImage(o.getImage(), o.getX(), o.getY(), this);
}
drawStrings(g2);
}
Also Jpanel constructor:
private void initBoard() {
addKeyListener(new TAdapter());
setFocusable(true);
setBackground(Color.WHITE);
setLayout(new GridBagLayout());
craft = new Craft(ICRAFT_X, ICRAFT_Y);
mapa = new Mapa();
setMinimumSize(new Dimension(WIDTH, HEIGHT));
initEnemiesAndAddThem();
czas = new Timer(delay, this);
czas.start();
}
JFrame constructor:
private void initGame()
{
add(new Plansza());
setTitle("Reeevah Raaid");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setSize(WIDTH, HEIGHT);
setPreferredSize(new Dimension(WIDTH, HEIGHT));
setLocationRelativeTo(null);
pack();
setVisible(true);
//setExtendedState(JFrame.MAXIMIZED_BOTH);
//setResizable(false);
}
You can scale the entire thing:
private void doDrawing(Graphics g) {
Graphics2D g2 = (Graphics2D) g;
AffineTransform originalTransform = g2.getTransform();
g2.scale(getWidth() / (double) WIDTH, getHeight() / (double) HEIGHT);
// Do all drawing here
g2.setTransform(originalTransform);
}
Some notes:
The g2.setTransform(originalTransform) at the end is important. The Graphics object does not belong to you—it may be used to draw other components, so you need to leave it in the same state it had when it was given to you.
WIDTH and HEIGHT need to be cast to double, because in Java, if any operand of a division operation is a double, the result is double, but if they are both int, integer division is performed, which is definitely not what you want. (For instance, 3 / (double) 2 is 1.5, but 3 / 2 is exactly 1.)
Any image needs to be drawn as follows:
double fct=1.0*image.getWidth()/getWidth();
double fct2=1.0*image.getHeight()/getHeight();
if(fct2>fct) fct=fct2;
g2.drawImage(image, x, y, (int)(1.0*image.getWidth()/fct), (int)(1.0*image.getHeight()/fct), this);
This will leave a lot of empty area - but that is if you want to keep the aspect ratio.
--
To center the image you do the following:
int newWidth=(int)(1.0*image.getWidth()/fct);
int newHeight=(int)(1.0*image.getHeight()/fct);
g2.drawImage(image, getWidth()/2-newWidth()/2, getHeight()/2-newHeight()/2, newWidth, newHeight, this);
Related
I learned that if you override
protected void paintComponent(Graphics g)
You can get an image painted as the background of a class that extends javax.swing.JPanel.
In my code I have 2 instances of the same Class extending JPanel with almost exactly the same code just with a different position and background image in a second JPanel and while one gets the background the other one does not. Here is my code:
public class CardPanel extends JPanel {
private int x, y, width, height;
private BufferedImage background;
public CardPanel(int x, int y, int width, int height, BufferedImage background) {
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.background = background;
createCardPanel();
}
private void createCardPanel() {
setPreferredSize(new Dimension(width, height));
setMaximumSize(new Dimension(width, height));
setMinimumSize(new Dimension(width, height));
setFocusable(false);
setOpaque(true);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(background, x, y, null);
}
}
And how I use it:
pCardPanel = new CardPanel();
dCardPanel = new CardPanel();
Declaring the CardPanels
private void createCardPanels(String imgPath) {
BufferedImage background = ImageLoader.loadImage(imgPath);
pCardPanel = new CardPanel(0, (height - Card.CARD_HEIGHT), width, Card.CARD_HEIGHT, background.getSubimage(0, (height - Card.CARD_HEIGHT), width, Card.CARD_HEIGHT));
dCardPanel = new CardPanel(0, 0, width, Card.CARD_HEIGHT, background.getSubimage(0, 0, width, Card.CARD_HEIGHT));
this.add(pCardPanel, BorderLayout.SOUTH);
this.add(dCardPanel, BorderLayout.NORTH);
}
Method for creating and adding the CardPanels
createCardPanels("/textures/background.png");
Using the method
public void addCardImage(BufferedImage img, boolean playerCard) {
JLabel imgLabel = new JLabel();
ImageIcon icon;
icon = new ImageIcon(img);
imgLabel.setIcon(icon);
cardImages.add(imgLabel);
if (playerCard)
pCardPanel.add(imgLabel);
else
dCardPanel.add(imgLabel);
display.pack();
}
This last method is called for adding Card Images to te panel, this part works. Now to my problem:
this is how it looks. (there are some other flaws like the card position but this will be a later issue I can fix myself)
As you can see, the panel on the bottom (pCardPanel) has no background image. Any ideas why it might be this way? Thanks in advance
You can get an image painted as the background of a class that extends javax.swing.JPanel
A background generally implies that the image fills the entire panel and the size of the panel is the same as the size of the image. Therefore when you paint the image the code should be:
g.drawImage(background, 0, 0, null);
So the image is always painted at the top left of the panel.
When the panel is added to the frame, the layout manager will set the location of the panel.
just with a different position
pCardPanel = new CardPanel(0, (height - Card.CARD_HEIGHT),
I would guess the problem is the "y" value is outside the size of the panel, so you don't see the image.
That is your preferred size does not account for the fact that you are attempting to paint the image at some location other than (0, 0) in which case the preferred size should be something like:
setPreferredSize(new Dimension(x + width, y + height));
However, you don't want to do that, since each component should be independent of other components. It should not know or care that you are trying to position two panels above/below one another. It should just worry about painting its own image and let the layout manager worry about setting the location of each panel.
So what you really want to do is just paint the image at (0, 0) and let the layout manager determine the location of the panel.
You are already using the BorderLayout. So it is the job of the layout manager to set the location of the component in the "SOUTH" to some non-zero "y" value.
I'm trying to achieve camera shake for my game by randomly setting the location of the JPanel which everything in the game is drawn on. After a bit of experimentation, I am certain that JPanel.setLocation(Point p) triggers a repaint, which I don't want to happen.
So the way I create screen shake is by specifying the intensity and the frames it should last. However, the effect always wore off far too quickly, so I did some experimentation. I found that the paintComponent(Graphics g) method of the JPanel was triggered multiple times within one frame, but only while there was screen shake (how really does not add much to the point).
This is how the effect is generated:
public void display(){
framesAlive++; //<-- used to track when the effect has worn off
int intensityX = (int) (Math.random() * vals[0] - vals[0] / 2);
int intensityY = (int) (Math.random() * vals[0] - vals[0] / 2);
pane.setLocation(new Point(intensityX, intensityY));
}
And this is the simplified version of the paintComponent method:
#Override
protected void paintComponent(Graphics g){
super.paintComponent(g);
for (int i = 0; i < stockEffects.size(); i++) {
stockEffects.get(i).display(g);
}
}
Again, my guess is that setLocation() causes a repaint, which basically results in an infinite loop in which the paintComponent() method triggers the display() function, which triggers setLocation(), which triggers a repaint that starts the whole cycle again. This results in the framesAlive variable being incremented multiple times per frame, which throws the whole timing system off. Is there an elegant way to solve this?
you can use AffineTransform. this don't have to change objects real location.
it just change how to draw.
you can shake, rotate, flip, scale etc....
public static void main (String[] arg) {
MainFrame mainFrame = new MainFrame();
mainFrame.setVisible(true);
}
public static class MainFrame extends JFrame{
public MainFrame() {
this.setSize(600,600);
this.setLocationRelativeTo(null);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
MainPanel mainPanel = new MainPanel();
this.add(mainPanel);
}
}
public static class MainPanel extends JPanel{
public void paint(Graphics g) {
super.paint(g);
// Panel Size = 400 X 400
g.drawLine(200, 0, 200, 400); // Y Axis
g.drawLine(0, 200, 400, 200); // X Axis
// Create Transform
AffineTransform at = new AffineTransform();
at.translate(200, 200); // Move Center Form (0, 0) To JPanel Center (200, 200)
// Change Transform
at.translate(-200, 0); // Move Center
// Set Transform To Graphics2D
Graphics2D g2d = (Graphics2D) g;
g2d.setTransform(at);
// Draw Rectangle By Graphics2D
g2d.fillRect(100, 100, 100, 100);
}
}
I am trying to draw a circle for a game I am making for a class.
This is my code:
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D)g;
// Assume x, y, and diameter are instance variables.
Ellipse2D.Double circle = new Ellipse2D.Double(50, 50, 150, 150);
}
However, when I run the code, it is not seen (My frame is 300 by 300)
I'm trying to embed a TTF font and then use draw it with Grapics2D. I've been able to create the font, but I'm not exactly sure how to pass the font to setFont. I make a new font here, which throws no exceptions:
private Font pixel = Font.createFont(Font.TRUETYPE_FONT, this.getClass().getResourceAsStream("font/amora.ttf"));
But I can't figure out how to draw it with setFont();
Here's my code:
private static final long serialVersionUID = 1L;
private Timer timer;
private Char Char;
private Font pixel = Font.createFont(Font.TRUETYPE_FONT, this.getClass().getResourceAsStream("font/amora.ttf")); <<<--------
public Board() throws FontFormatException, IOException {
addKeyListener(new TAdapter());
setFocusable(true);
setBackground(Color.BLACK);
setDoubleBuffered(true);
Char = new Char();
timer = new Timer(5, this);
timer.start();
}
public void paint(Graphics g) {
super.paint(g);
Graphics2D g2d = (Graphics2D)g;
g2d.drawImage(Char.getImage(), Char.getX(), Char.getY(), this);
g.setColor(Color.white);
g.setFont( What goes here? ); // <------------
g.drawString("Amora Engine rev25 (acetech09)", 10, 20);
g.drawString(Char.getDebugStats(0), 10, 40);
g.drawString(Char.getDebugStats(1), 10, 60);
Toolkit.getDefaultToolkit().sync();
g.dispose();
}
public void actionPerformed(ActionEvent e) {
Char.move();
repaint();
}
}
Any help would be greatly appreciated. Thanks.
You could just do...
g.setFont(pixel);
But you might have better sucess with
g.setFont(pixel.deriveFont(Font.BOLD, 36f));
Are variations of....
Also, don't dispose of a Graphics context you did not create...
Graphics2D g2d = (Graphics2D)g;
/*...*/
// g.dispose();
Or
Graphics2D g2d = (Graphics2D)g.create();
/*...*/
g.dispose();
I'd also be loathed to override the paint method. Assuming you're using something like JComponent or JPanel, you should use paintComponent. If you're rendering directly yo a top level container (like JFrame), then I wouldn't. There are issues with double buffering and frame borders that won't make your life fun...
I'm also concerned about new Timer(5, this) - 5 milliseconds is close enough to 0 to make little difference. You'd be better of with something like 40, which should give you something like 25fps or 17 which will give you roughly 60fps...
It should be
g.setFont( this.pixel );
If this is not working, try:
Commenting out the setFont instruction.
Replacing Font.createFont with a reference to a Java standard font.
to rule out possible issues.
I'm overriding the paintComponent method for a background in a JComponent and all is going well.
However, I want to start painting from the lower left corner instead of the upper left.
Do I need to transform something, or what?
Yes, you can use an AffineTransform to draw from the lower left corner:
Code:
public static void main(String[] args) {
JFrame frame = new JFrame("Test");
frame.add(new JComponent() {
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
// save the "old" transform
AffineTransform old = g2d.getTransform();
// update graphics object with the inverted y-transform
g2d.translate(0, getHeight() - 1);
g2d.scale(1, -1);
// draw what you want
g2d.drawLine(0, 0, 300, 200);
// restore the old transform
g2d.setTransform(old);
}
});
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(400, 300);
frame.setVisible(true);
}