Draw and redraw on a canvas in swing - java

I know there's no direct replacement for java.awt.Canvas in swing, and I know I'm supposed to use a JPanel and override paintComponent, for example like so:
public void paintComponent(Graphics g) {
g.setColor(Color.black);
g.drawOval(0, 0, 100, 100);
}
And this would draw a black circle on the JPanel when it is created. The problem I have is that I want a dynamic canvas: I want to be able to draw things in response to user input, and redraw continuously, not just once when the application starts. An example would be having a moving object on a canvas, that would need to be redrawn at a rate of say 60 frames per second. How could I achieve this without using AWT components?
EDIT: what I mean is, in an actual canvas, I'd be able to arbitrarily call, say, drawOval anywhere in my code, and that would draw an oval on the canvas; is this doable with JPanel?

Store the information to be drawn (e.g. a Shape or a group of them) and call repaint() from a Swing Timer. Each time the paintComponent(..) method is called, first call the super(..) method to erase the previous drawings, then iterate the list of shapes, move them if necessary, and draw each one.

Here's one way to do it:
public class Renderer extends JComponent implements ActionListener {
private int x;
public Renderer() {
Timer timer = new Timer(1000/60, this);
timer.start();
x = 0;
}
#Override
public void paintComponent(Graphics g) {
super.paint(g);
// drawing code
g.setColor(Color.black);
g.drawOval(x, 0, 100, 100);
}
private void update() {
this.x++;
}
#Override
public void actionPerformed(ActionEvent e) {
update();
repaint();
}
}
Now just add this to your component (JPanel or whatever):
comp.add(new Renderer());

Related

JPanel drawing is slowed down after several instances are painted

I'm building a game and I'm painting the road sprites with the JPanel's draw function. The roads (Building class) can be built by dragging the mouse and on each field a new road sprite appeares. But after I've drawn like 20 road sprites, the drawing gets really slow.
I have a frame and there is this JPanel on it.
Here is the code of the JPanel on which my game drawing is:
private class GamePanel extends JPanel implements ActionListener{
Field[][] map = gameEngine.getMap().getFields();
ArrayList<Building> buildings = gameEngine.getBuildings();
Timer timer;
ArrayList<Field> fields = new ArrayList<>();
GameFrame frame; //REFERENCE FOR THE CONTAINER OF THIS PANEL
private int mousePosX;
private int mousePosY;
GamePanel(GameFrame frame){
/*...*/
Mouse mouseListener = new Mouse();
addMouseListener(mouseListener);
addMouseMotionListener(mouseListener);
timer = new Timer(1000/30,this);
timer.start();
/*...*/
}
public void paintComponent(Graphics g) {
super.paintComponent(g);
draw(g);
}
public void draw(Graphics g){
for(Building b : buildings){
int drawPosX = b.getLocation().getPos().x*40;
int drawPosY = b.getLocation().getPos().y*40;
try {
BufferedImage img = ImageIO.read(new File("src/GFX/" + b.getType() + ".png"));
g.drawImage(img, drawPosX, drawPosY, null);
} catch (IOException e) {
}
}
}
#Override
public void actionPerformed(ActionEvent e) {
// I thought this is not needed because I call "repaint()" only at mouse events (like
// building road by dragging)
}
public class Mouse extends MouseAdapter{
#Override
public void mouseDragged(MouseEvent evt){
repaint(); // THIS IS AN EXAMPLE TO WHERE I CALL THE REPAINT()
fieldPosX = evt.getX() - (evt.getX() % 40);
fieldPosY = evt.getY() - (evt.getY() % 40);
gameEngine.placeRoad(new SimpleRoad(new Field(fieldPosX/40,fieldPosY/40)));
}
/*... OTHER MOUSE EVENTS ...*/
}
I thought that calling repaint() only at mouse events will optimise the speed but it really isn't. I attached a GIF on which it can be seen that after 2 line of roads, it gets really slow.
I heard about invokeLater and people advised me to use it but I don't know how to implement that in this project. Why is my game getting slower after several buildings, where am I making a mistake? Would invokeLater solve the problem? How do I place it in my project?
Thanks for helping!
Why is my game getting slower after several buildings,
try
{
BufferedImage img = ImageIO.read(new File("src/GFX/" + b.getType() + ".png"));
g.drawImage(img, drawPosX, drawPosY, null);
}
Don't do I/O in a painting method. As you add more building you are doing more I/O.
The images should be read in the constructor of your class.
You can save them in a HashMap for easy access in the painting method.
Or, the image can be saved as part of the Building class itself.

Is there a way to implement floating damage numbers with java.swing?

I'm creating a game using Java Swing, and I'm finding the need for graphical displays of what's going on at this point. The current display uses a grid of JButtons to represent the tiles on a battlefield. Would it be possible to display floating/disappearing damage numbers over the JButtons, using perhaps custom made pixel art GIFs? If so, how would I go about implementing this?
Almost all Swing components can be extended to change its presentation.
Create an own button class extending JButton and override its paintComponent method to display the required damage. This method receives a Graphic (actually an instance of Graphics2D) on which you can draw the damage, if one is active.
Very simple example:
public class DamageButton extends JButton {
private String damage = null;
public DamageButton(String text) {
super(text);
}
public void setDamage(String damage) {
this.damage = damage;
System.out.println(damage);
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
if (damage != null) {
Graphics2D gg = (Graphics2D) g.create();
try {
gg.setColor(Color.RED);
gg.drawString(damage, 10, 10);
} finally {
gg.dispose();
}
}
}
}
Creating a new Graphics (gg) so the settings of g are not changed and need not to be restored.
Call setDamage() with some text to have it displayed over the button or with null to cancel the effect.
Animation or other effects can (more or less) easily be added. Just be aware that the given Graphics2D has a clipping area set to the dimension of the button.

Java graphics being drawn on top of existing graphic

Main class:
public Main() {
Frame f = new Frame();
final Panel p = f.p;
final Player player = new Player();
Timer t = new Timer(UPDATE_PERIOD, new ActionListener() {
public void actionPerformed(ActionEvent e) {
Graphics g = p.getGraphics();
p.render(g);
player.tick();
player.render(g);
g.dispose();
}
});
t.start();
}
Player render method:
public void render(Graphics g) {
g.drawImage(Images.get("player"), x, y, null);
}
The problem is, that all previous drawn images are still there. Example (when I change the drawn image's x or y):
To draw in Swing, you should not be getting the Graphics object directly from the JPanel. Instead, override the paintComponent method and use the parameter Graphics object to perform your custom drawing, with a call to the parent method to erase previous painting
#Override
public void paintComponent(Graphics g){
super.paintComponent(g);
//custom painting goes here
}
If you wish to trigger a repaint, use the method by that name on the JPanel:
p.repaint();
Rather than doing custom rendering your your timer, you should really be doing all your painting in your paintComponent method. Something like:
public void actionPerformed(ActionEvent e) {
player.tick();
p.repaint();
}
And then re-render the player and the background in paintComponent()
Painting like you currently are runs into issues when you resize the panel, etc
Try calling 'p.repaint()' in your ActionListener once you have changed the position of the Graphic.

Unexcpected output while trying to draw line in java

This is my first GUI exercise. I am trying to draw a line using a for loop but for some reason I haven't figured out why I'm getting only its last dot (pixel). I guess repaint() does something different than what I thought but I can't figure out yet what it is.
here is my code:
package com.mycompany;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class MousePanel extends JPanel implements MouseListener{
int pointX, pointY, oldX, oldY;
public MousePanel(){
super();
addMouseListener(this);
}
public void mouseClicked(MouseEvent mouse){
// Tell the panel that we need to redraw things.
oldX=pointX;
oldY=pointY;
// Get the location of the current mouse click.
pointX = mouse.getX();
pointY = mouse.getY();
// Tell the panel that we need to redraw things.
for (int i=0 ; i<50 ; i++)
{
pointX ++;
repaint();
}
System.out.println("x:"+pointX+", y:"+pointY);
}
public void paintComponent(Graphics g){
g.fillOval(pointX, pointY, 5, 5);
}
public void mouseEntered(MouseEvent mouse){ }
public void mouseExited(MouseEvent mouse){ }
public void mousePressed(MouseEvent mouse){ }
public void mouseReleased(MouseEvent mouse){ }
public static void main(String arg[]){
JFrame frame = new JFrame("MousePanel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(640,400);
MousePanel panel = new MousePanel();
frame.setContentPane(panel);
frame.setVisible(true);
}
}
the
Java Code:
for (int i=0 ; i<50 ; i++)
{
pointX ++;
repaint();
}
I'm getting only its last dot (pixel).
That is all you paint in your paintComponent() method.
If you want all the ovals then you need to repaint all the ovals every time the paintComponent() method is invoked.
See Custom Painting Approaches for the two common ways to do this:
Keep track of all the objects to be painted in an ArrayList and then iterate through the List every time paintComponent() is invoked
Paint your objects to a BufferedImage and then just paint the BufferedImage in the paintComponent() method.
The repaint will always draw the fixed end-state of the component..
And, since you request the repaint in a single callback, you will get the following
x=1
repaint
x=2
repaint
...
etc.
The repaint itself does not occur until you have finished the loop, and the next UI event can be processed (which is your repaint request). The 50 or so repaint requests are probably conflated into one, which again calls your paintComponent.
Now the paint see that it should paint a 5px oval using your current x value, and does so.
So you would probably replace the for loop with a single repaint request, and change the paintComponent to paint between oldX,oldY and pointX,pointY
You are repainting the oval 50 times. I'm not very sure what you want to do. If you wish to fill an oval with a width/height of 50 pixels, you could update the method paintComponent
public void paintComponent(Graphics g){
g.fillOval(pointX, pointY, 50, 50);
}
In this case the for loop is not necessary, only a repaint is enough.
If you want to draw a line, there is the method drawLine. I see you have the old pixels position stored so you could go for
public void paintComponent(Graphics g){
g.drawLine(pointX, pointY, oldX, oldY);
}
Again, only a repaint is necessary and no loop. I've posted the code below
public class MousePanel extends JPanel implements MouseListener {
int pointX, pointY, oldX, oldY;
public MousePanel(){
super();
addMouseListener(this);
}
public void mouseClicked(MouseEvent mouse){
// Tell the panel that we need to redraw things.
oldX=pointX;
oldY=pointY;
// Get the location of the current mouse click.
pointX = mouse.getX();
pointY = mouse.getY();
// Tell the panel that we need to redraw things.
repaint();
System.out.println("x:"+pointX+", y:"+pointY);
}
public void paintComponent(Graphics g){
g.drawLine(pointX, pointY, oldX, oldY);
}
public void mouseEntered(MouseEvent mouse){ }
public void mouseExited(MouseEvent mouse){ }
public void mousePressed(MouseEvent mouse){ }
public void mouseReleased(MouseEvent mouse){ }
public static void main(String arg[]){
JFrame frame = new JFrame("MousePanel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setSize(640,400);
MousePanel panel = new MousePanel();
frame.setContentPane(panel);
frame.setVisible(true);
}
}

Java making a 'dot/pixel' In swing/awt

I want to know how to make a dot/pixel at a certain x,y co-ordinate on my JFrame.
Anyone know some simple code for this?
I have created a small example program:
public class Test extends JFrame {
public Test() {
this.setPreferredSize(new Dimension(400, 400));
this.pack();
this.setVisible(true);
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
}
#Override
public void paint(Graphics g) {
super.paint(g);
// define the position
int locX = 200;
int locY = 200;
// draw a line (there is no drawPoint..)
g.drawLine(locX, locY, locX, locY);
}
public static void main(String[] args) {
Test test = new Test();
}
}
You could also use the update or paintComponents method which would be much nicer. But then you have to make sure, that it gets called. If you have problems and it does not get called you could use the following solution: Why is paint()/paintComponent() never called?
Best compromise between simplicity and usefulness would probably be to extend JPanel, and override paintComponent( Graphics ). Then place that panel in your JFrame (with an appropriate layout. There are some usage notes here: http://download.oracle.com/javase/1.4.2/docs/api/javax/swing/JComponent.html#paintComponent%28java.awt.Graphics%29
see
void update(Graphics g)
method of JFrame class.
graphics API ( like draw point, draw line, draw arc, etc ) are in Graphics class.
EDIT: http://www.javadb.com/drawing-a-line-using-java-2d-graphics-api
Ask yourself if your really want to extend JFrame or JPanel. If you decide that you don't then you could create a basic JComponent. You may have varying success with this depending on what layout manager you use.
public class PixelComponent extends JComponent
{
private Color color;
public PixelComponent(Color color)
{
super();
this.color = color;
}
public PixelComponent()
{
this(Color.BLACK);
}
public void paintComponent(Graphics g)
{
super.paintComponent(g);
g.setColor(color);
g.fillRect(0, 0, 1, 1);
}
}
Send the Graphics Reference and axis x and y to make a pixel:
private void doPixel(Graphics g, int x, int y){
g.fillRect(x, y, 1, 1);
}

Categories

Resources