I am doing the initial section of a simple platform game in Java. I have created a class called entity which extends JPanel and successfully added it to the window.
import javax.swing.*;
import java.awt.*;
/**
* Created by bw12954 on 27/05/16.
*/
public abstract class Entity extends JPanel {
private final SpriteSheet sprites;
private Point location;
private Dimension dimensions;
public Entity(int x, int y, int w, int h, SpriteSheet sprites)
{
location = new Point(x, y);
dimensions = new Dimension(w, h);
this.sprites = sprites;
}
public Entity(int x, int y, int w, int h)
{
this(x, y, w, h, null);
}
#Override
public Dimension getPreferredSize()
{
return dimensions;
}
public void setLocation(int x, int y)
{
location.setLocation(x, y);
}
/* Some code removed here for brevity */
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
g.drawImage(sprites.get(),
(int)location.getX(),
(int)location.getY(),
null);
}
}
If I add this directly to the JFrame as below, then the graphic shows up on the window as I would expect it to (note that Player is a very simple subclass of Entity)
public class Window {
private JFrame window;
public Window()
{
SwingUtilities.invokeLater(this::run);
}
private void run()
{
try {
window = new JFrame();
window.setDefaultCloseOperation(window.EXIT_ON_CLOSE);
window.setLocationByPlatform(true);
window.setUndecorated(true);
Player p = new Player(0,0);
window.add(p);
window.setExtendedState(JFrame.MAXIMIZED_BOTH);
window.setVisible(true);
} catch (IOException e) {
// TODO handle exception
e.printStackTrace();
}
}
}
However - when I create a class called World which also extends JPanel, add that to the window instead, then use the add() method in its constructor to add a new Player to it, it doesn't appear. Interestingly, if I add setBackground() to the constructor for Player/Entity, I can see a coloured square where the entity SHOULD be. Its just that drawImage doesn't appear to work.
If anyone else has any idea what is going on here, it will be greatfully appreciated!
If I add this directly to the JFrame as below, then the graphic shows up on the window as I would expect it to
The default layout manager of the content pane of the frame is a BorderLayout. Any component added to the frame without a constraint will be added to the "CENTER" which means the component is automatically resized to fill the entire space.
However - when I create a class called World which also extends JPanel, add that to the window instead, then use the add() method in its constructor to add a new Player to it, it doesn't appear.
The default layout manager of a JPanel is a FlowLayout, which respects the preferred size of any component added to it and will reset the location of the component based on the rules of the layout manager.
Interestingly, if I add setBackground() to the constructor for Player/Entity, I can see a coloured square where the entity SHOULD be. Its just that drawImage doesn't appear to work
Probably because your preferred size calculation is incorrect and the image is truncated.
location = new Point(x, y);
dimensions = new Dimension(w, h);
The above code will only work if the Point is (0, 0). The more general case code should be:
location = new Point(x, y);
dimensions = new Dimension(x + w, y + h);
because you need to consider where you actually paint the image realative to the component.
So when you do this you should see the image, however you will not see the image in the proper location because the layout manager will override the location.
So it you want to continue with this approach of using components you will need to use a null layout, which means you will manually need to use the setSize(...) and setLocation(...) of each component. In this case the size of the component will be the (w, h) and you will draw the image using:
g.drawImage(sprites.get(), 0, 0, this);
Note however if you use the component approach there is no need to even create a custom component. You could just use a JLabel with an ImageIcon. You would assign the Icon when you create the label.
Related
I'm new to Java and I want to try some graphic things with it. I want to generate two circles with two different colors and different positions. My code:
Paint Class:
package de.test.pkg;
import javax.swing.*;
public class paint {
public static void main(String[] args) throws Exception{
JFrame frame = new JFrame("Titel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Circle d = new Circle();
Circle r = new CircleRed();
frame.add(d);
frame.add(r);
frame.setSize(600,200);
frame.setVisible(true);
}
}
Circle class
package de.test.pkg;
import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
public class Circle extends JPanel {
private double iconRadius = 100;
private Color defaultColor = new Color(89,104,99);
private int positionX = 0;
private int positionY = 0;
private Ellipse2D iconBody = new Ellipse2D.Double(getPositionX(),getPositionY(),iconRadius,iconRadius);
public Icon(){
}
public Color getDefaultColor() {
return defaultColor;
}
public void setDefaultColor(Color defaultColor) {
this.defaultColor = defaultColor;
}
public int getPositionX() {
return positionX;
}
public void setPositionX(int positionX) {
this.positionX = positionX;
}
public int getPositionY() {
return positionY;
}
public void setPositionY(int positionY) {
this.positionY = positionY;
}
public void paintComponent(Graphics g) {
Graphics2D g2d = (Graphics2D)g;
super.paintComponent(g2d);
this.setBackground(new Color(255,255,255));
g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
g2d.setPaint(getDefaultColor());
g2d.draw(iconBody);
g2d.fill(iconBody);
}
}
CircleRed class
package de.design.pkg;
import java.awt.Color;
public class CircleRed extends Circle {
private Color defaultColor = Color.RED;
private int positionX = 120;
private int positionY = 0;
public CircleRed() {
}
public Color getDefaultColor() {
return defaultColor;
}
public void setDefaultColor(Color defaultColor) {
this.defaultColor = defaultColor;
}
public int getPositionX() {
return positionX;
}
public void setPositionX(int positionX) {
this.positionX = positionX;
}
public int getPositionY() {
return positionY;
}
public void setPositionY(int positionY) {
this.positionY = positionY;
}
}
The Result is that the Circles have the same positions.
My questions are:
Why do they have the same positions?
Is this a good way to do that or are there better solutions? I want to use a class so please don't gave me answers with do all that paint thing in the Main.
Is there a better way to hold the position. Maybe in an array? But how should the setter and getter look like if I want to return array[position]?
If I want to set the Position from the Main function. How can I do this?
The Result is that the Circles have the same positions.
(1) Why do they have the same positions?
Not really. The result is that only CircleRed is displayed. Your problem here is not with painting, it's with adding components to a container with a suitable layout manager. The lines
Circle d = new Circle();
Circle r = new CircleRed();
frame.add(d);
frame.add(r);
add r instead of d. This is because JFrame uses BorderLayout by default and you are replacing the center component d with r the line after. Just to show the point, add the line
frame.setLayout(new GridLayout(1, 2));
(2) Is this a good way to do that or are there better solutions? I want to use a class so please don't gave me answers with do all that paint thing in the Main.
It depends on what you are aiming to do. I would venture a guess that if you want to practice inheritance, it would be better for you to create an abstract class for a general circle with shared code and then subclass it with concrete implementation and specific code for the various types. Otherwise, you can just create a single customizable circle class and instantiate that one with different parameters.
It's not a good practical approach in any case because the placement of the circles (which are JPanels) will be determined by the layout manager. The painting only determines the location of the painted shape in the panel. It would be better to just paint the shapes on a single big panel and not with using multiple panels.
There are a few very good answers on the site about moving components around.
(3) Is there a better way to hold the position. Maybe in an array? But how should the setter and getter look like if i want to return array[position]?
There are effectively 2 positions in your design. One is for the panels in the frame, the other is for the shapes in the panels.
For the latter, I would use a Point or just an int x, y fields in the class itself. Getters and setters are the standard ones, the setters will control the position (you will need to call repaint() though).
For the first, it is determined by the layout manager and you don't (shouldn't) control it in a pixel-prefect way. You just instruct the layout manager with "guidelines" and it does the calculations for you.
(4) If I want to set the Position from the Main function. How can i do this?
Again, depends on which position you are talking about. See above.
What your doing is very overkill for just creating two colored circles. You can just use the paint method in java.awt
public void paint(Graphics g){
g.setColor(Color.YELLOW);
g.fillOval(20,20,160,160);
g.setColor(Color.RED);
g.fillOval(60,60,80,80);
}
fillOval takes the following parameters (int x, int y, int width, int height)
You can use g.setColor(Color.NAME) to change the color of your circle. Just call this method before your draw calls.
They're in the same position because the paintComponent() method in Circle is being used for both - and it's using the position variables defined in Circle. When you have CircleRed extend Circle, you don't need to define the position variables again - you inherit them from Circle. Instead, call the setPosition() methods on CircleRed. (You don't need to define these either, they're also inherited.)
There are a variety better solutions, I think the biggest improvement would be to improve your use of inheritance.
That's a fine way to hold the position. If anything else, you could also use a Point object. (Already in Java)
To set the position from the main function, you just call for example
Circle c = new Circle();
c.setPositionX(120);
c.setPositionY(40);
You are going wrong way. You can simply draw two circles by overriding paint method of JFrame class.
Here is a sample demo program.
import java.awt.*;
/**
*
* #author Pankaj
*/
public class TestFrame extends javax.swing.JFrame {
/**
* Creates new form NewJFrame
*/
public TestFrame() {
initComponents();
setTitle("Graphics Demo");
setSize(200,200);
}
/**
* You need to override this method.
*/
#Override
public void paint(Graphics g) {
int X1 = 10; //X coordinate of first circle
int X2 = 60; //X coordinate of second circle
int Y1 = 100; //Y coordinate of first circle
int Y2 = 100; //Y coordinate of second circle
int width = 50; //Width of the circle
int height = 50; //Height of the circle
Color color1 = Color.RED; //Color of first circle
Color color2 = Color.BLUE; //Color of second circle
g.setColor(color1);
g.fillOval(X1, Y1, width, height);
g.setColor(color2);
g.fillOval(X2, Y2, width, height);
}
private void initComponents() {
setDefaultCloseOperation(javax.swing.WindowConstants.EXIT_ON_CLOSE);
setResizable(false);
pack();
}
public static void main(String args[]) {
/* Create and display the form */
java.awt.EventQueue.invokeLater(new Runnable() {
public void run() {
new TestFrame().setVisible(true);
}
});
}
}
And here's the output
If I create a new class which inherits from JComponent, whose paintComponent(Graphics g) method I override, by painting a circle using g, what should I modify in order for a MouseListener to trigger only when I click inside the bounds of the component?
Because in the constructor of my component I added setBounds(...) and then added a MouseListener, but it fires every time I click anywhere inside the container in which my custom component is and not only when I click inside it.
I do not want to check in the mouseClicked() method whether the event happened inside my component or not, I want it to be called only when the click was inside.
Here's my code:
public class Node extends JComponent {
private int x, y, radius;
public Node(int xx, int yy, int r) {
x = xx;
y = yy;
radius = r;
this.setBounds(new Rectangle(x - r, y - r, 2 * r, 2 * r));
setPreferredSize(new Dimension(2 * r, 2 * r));
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D gr = (Graphics2D) g;
gr.setColor(Color.BLACK);
gr.drawOval(x - radius, y - radius, 2 * radius, 2 * radius);
}
public static void main(String[] args) {
final JFrame f = new JFrame();
f.setSize(new Dimension(500, 500));
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final JPanel p = new JPanel();
p.setLayout(new BorderLayout());
Node n = new Node(100, 100, 25);
n.addMouseListener(new MouseAdapter() {
#Override
public void mouseClicked(MouseEvent e) {
super.mouseClicked(e);
System.out.println("clicked");
}
});
p.add(n);
f.add(p);
f.setVisible(true);
}
}
Your mouse listener is working correctly as it is only functioning within the bounds of your JComponent. To prove it to yourself, put a Border around your component to see what it actually covers:
public Node(int xx, int yy, int r) {
//. ....
setBorder(BorderFactory.createTitledBorder("Node"));
}
Understand that you're component is being added to a BorderLayout-using container in the default (BorderLayout.CENTER) position, and thus it fills the container. It does not matter that you set the bounds of your component (something that you should not be doing) or set its preferred size (also something that usually should be avoided).
For my money, I'd make my Node a logical class, one that implements the Shape interface, not a class that extends a JComponent, and then I could use the Shape's contains(Point p) method whenever I need to know if my Node has been clicked.
I am trying to display a background image in a JFrame, but the image isn't showing up and I can't find what the error in the code is.
public class GraphicsComponent extends JComponent
{
int xPosition = 200;//initializes and declares the x coordinate
int yPosition = 400;//initializes and declares the y coordinate
Image i;
public GraphicsComponent()
{
try{
File sky = new File("lib/background.png");
i = ImageIO.read(sky);
}
catch (Exception e)
{
e.printStackTrace();
}
}
public void paintComponent(Graphics g) //overrides paintComponent method in JComponent
{
if (i == null) return;
g.drawImage(i, 0, 0, this);
}
Why are you creating a custom component? If you create a custom component then you also need to override the getPreferredSize() method of the component to return the size of the image so the layout manager can paint the component at its proper size.
So the size of the component is probably zero, so there is nothing to paint.
The easiest solution is to just use a JLabel with an ImageIcon and let the layout manager deal with the size issue.
Here is what I am trying to do. I have extended JButton and overwritten the paintComponent method creating my desired effect of a rounded edge button, and a color fading effect when the button is rolled over by a mouse. All that works great. My problem is that the JButton is still painting a white rectangle area as the images show.
I would like 1) the white corners to be gone and 2) the cetner of the button to show the panel behind it. Here is what I have tried:
1- when painting the button use getParent().getBackground() and paint the button first. This works great for opaque panels. However I would love this button to work on a partially or fully transparent panel. With transparent panels it paints the color, but on the white background hiding anything behind the panel (like an image).
2- I have tried many combinations of setOpaque(false) or setContentAreaFilled(false). I have tried this while calling super.paintComponent(g) and not calling it. None of those seem to work.
3- The button looks right when I don't use the method g2.clearRect(0,0,width,height) (clearing the graphics area before painting), but since the graphics object is never covered up the fade effect stops working after one rollover of the button.
4- I use a JLabel for the text and have tried setting it opaque or just not using it and the issue still remains. so I don't think that is the issue.
Since I only want an affect for the JButton and not other swing components I'm really hoping to avoid making my own ButtonUI.
Thanks and I hope this makes sense. Below is the code for my button.
import javax.swing.*;
import javax.swing.Timer;
import java.awt.*;
import java.awt.event.*;
/**
* WButton.java
*
* An extension of JButton but with custom graphics
*
*/
public class WButton extends JButton{
private Timer timer;
private float[] background = {.3f,.6f,.8f,0f};
private boolean fadeUp = true;
private boolean fadeDown = false;
private JLabel label;
/**
* Default Constructor
*/
public WButton(){
super();
label = new JLabel();
setupButton();
}
/**
* Text constructor
*/
public WButton(String text){
super(text);
label = new JLabel(text);
setupButton();
}
/**
* common setup functions
*/
private void setupButton(){
timer = new Timer(24,new TimerAction(this));
label.setLabelFor(this);
add(label);
}
/**
* Set the background color
*/
#Override
public void setBackground(Color bg){
background = bg.getRGBComponents(background);
background[3] = 0f;
super.setBackground(new Color(background[0],background[1],
background[2],background[3]));
repaint();
}
/**
* get background
*/
#Override
public Color getBackground(){
if(background!=null)
return new Color(background[0],background[1],background[2],background[3]);
return new Color(.5f,.5f,.5f);
}
/**
* Set the font of the button
*/
#Override
public void setFont(Font font){
super.setFont(font);
if(label!=null)
label.setFont(font);
}
/**
* Override the set text method
*/
#Override
public void setText(String t){
super.setText(t);
if(label!=null)
label.setText(t);
}
/**
* Paint the button
*/
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
int width = getWidth();
int height = getHeight();
Graphics2D g2 = (Graphics2D)g;
g2.clearRect(0,0,width,height);
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
RenderingHints.VALUE_ANTIALIAS_ON);
//Check Button Model state
if(model.isPressed())
paintPressedButton(g2,width,height);
else{
if(model.isRollover()){
if(fadeUp){
fadeUp = false;
timer.start();
}
}
else{
if(fadeDown){
fadeDown = false;
timer.start();
}
}
g2.setPaint(new Color(background[0],background[1],background[2],background[3]));
g2.fillRoundRect(0,0,width-1,height-1,height,height);
}
}
/**
* Draw a pressed button
*/
private void paintPressedButton(Graphics2D g2,int width,int height){
float[] temp = new float[4];
for(int i=0;i<background.length;i++)
temp[i] = background[i]-.4f < 0f ? 0f : background[i]-.4f;
g2.setPaint(new Color(temp[0],temp[1],temp[2],temp[3]));
g2.fillRoundRect(0,0,width-1,height-1,height,height);
}
/**
* paint the border
*/
public void paintBorder(Graphics g){
int width = getWidth();
int height = getHeight();
g.setColor(Color.BLACK);
g.drawRoundRect(0,0,width-1,height-1,height,height);
}
/**
* Inner action listener class
*/
private class TimerAction implements ActionListener{
private float alphaInc = .2f;
WButton button;
public TimerAction(WButton b){
button = b;
}
public void actionPerformed(ActionEvent e){
if(model.isRollover()){
background[3] += alphaInc;
if(background[3] > 1.0f){
timer.stop();
background[3] = 1.0f;
fadeDown = true;
}
}
else{
background[3] -= alphaInc;
if(background[3] < 0f){
timer.stop();
background[3] = 0f;
fadeUp = true;
}
}
button.repaint();
}
}
}
EDIT 1
What aly suggested got me closer, but not quite there. Instead of g2.clearRect() I painted the object with a transparent color as suggested. The white box is gone, but a different color is there. Upon investigation is is the color of the parent panel but with no transparency. Here are pictures for an example (the panel has 70% transparency). The first pictures is when the program starts. The second picture is after 1 mouse rollover.
What you could do is instead of using clearRect(), clear the background with a completely transparent color.
g2.setColor(new Color(0,0,0,0));
g2.drawRect(0,0,width,height);
You still need to setOpaque(false) on the JButton so that it doesn't use the blue rollover color as the background once you hover over it once.
Edit: After seeing what you just posted, I think the problem is that the main frame isn't repainted.
Try:
SwingUtilities.getWindowAncestor(this).repaint();
in the paint method to repaint the frame, that might fix the problem.
The problem here is, swing uses paintImmediately(x, y, width, height) on JButton to repaint the changed area. If you look inside that method, it iterates through the parent hierarchy (if parent is not opaque) until it finds an opaque component. Then call repaint on it. As you may notice, this approach don't take the alpha component in background color into account because those components are opaque (isOpaque = true).
To address this issue, you can override the paintImmediately() method in JButton as follows:
#Override
public void paintImmediately(int x, int y, int w, int h) {
super.paintImmediately(x, y, w, h);
Component component = this;
boolean repaint = false;
while (!component.isOpaque() || (component.getBackground() != null && component.getBackground().getAlpha() < 255)) {
if (component.getBackground() != null && component.getBackground().getAlpha() < 255) {
repaint = true;
}
if (component.getParent() == null) {
break;
}
component = component.getParent();
if (!(component instanceof JComponent)) {
break;
}
}
// There is no need to repaint if no component with an alpha component in
// background is found and no parent component is found (implied by component != this)
// since super.paintImmediately() should have handled the general case.
if (repaint && component != this) {
component.repaint();
}
}
This method will check for the parent of the topmost component which is either not opaque or has a transparent background and repaints it. Performance point of view, this is much better than the currently accepted answer (which is redrawing the entire window, everytime when a button is hovered/pressed).
I have a picture that details what I want to achieve for all values of Game.height. Problem is my mathematical expression in the variable circle_dy seems to be incorrect when it comes to scale the image's location for all values of Game.height.
// images of the game
private Image circle;
// ball's spawning location
private int circle_dx = 0;
private int circle_dy = (Game.height/2) + 30 ;
public class GameBoard{
public GameBoard(){
// construct an ImageIcon specified by the given string directory
ImageIcon circle = new ImageIcon("src/pics/circle.png");
// get image type of the ImageIcon and assign it into the image instance //variable
this.circle = circle.getImage();
setBackground(Color.black);
}
// appropriate method to paint is paintComponent
public void paintComponent(Graphics g){
// if you are overriding a method call the super class paintComponent method
// because you are creating your own version of the paintComponent method
super.paintComponent(g);
// draw the image itself at a particular location on the JPanel
g.drawImage(this.circle,circle_dx,circle_dy,this);
}
}
public class Game extends JFrame{
public static final int width = 600;
public static final int height = 200;
public Game(){
// add the JPanel to the jframe
add(new GameBoard());
setVisible(true);
// set the size of the jframe
setSize(width,height);
}
}
As a suggestion:
Write a listener for windows resize that will update the size of your ball.
To calculate the required size changes, grab the x and y sizes of your window (would suggest saving them each time listener finishes). Work out the old and new values as a % then scale your ball by the same %.
For reference: http://docs.oracle.com/javase/tutorial/uiswing/events/componentlistener.html