How do I draw the same moving image multiple times? - java

Hello I'm making a platformer game and when you press space the character shoots a fireball that moves across the screen but when you press space again the fireball's coordinates are set back to the player's coordinates rather than drawing another fireball which is what I want.
import java.awt.*;
import javax.swing.*;
public class Fire extends JPanel{
Image fireball;
private int x=155000,y=155000;
Player player = new Player();
public void paint(Graphics g){
g.drawImage(fireball, x, y, null);
}
public Fire(){
}
public void update(){
fireball = new ImageIcon("C:\\Users\\steven.greens10\\Desktop\\Programs\\Raw Java\\Platform\\res\\fireball.png").getImage();
x+=5;
if(x > 640){
x=155000;
}
}
public void shoot(Player p){
x = p.getX();
y = p.getY();
repaint();
}
}

#KevinWorkman is right. You need some kind of data structure to hold the fireballs. In the example below I used a List of Fireball.
List<Fireball> fireBalls;
...
private class Fireball {
Image fireball;
int x = 150;
int y = 125;
public Fireball(Image image) {
fireball = image;
}
public void drawFireball(Graphics g) {
g.drawImage(fireball, x, y, 50, 50, null);
}
}
To paint them, I just iterate through them. To make them move forward I just increas the x value in the timer and call repaint
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
...
for (Fireball ball : fireBalls) {
ball.drawFireball(g);
}
}
Here's the complete code
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.*;
import java.util.List;
import java.util.logging.*;
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.Timer;
public class WannaBeStreetFighter extends JPanel {
private static final int D_W = 700;
private static final int D_H = 250;
private static final int X_INC = 10;
List<Fireball> fireBalls;
BufferedImage ryu;
BufferedImage fireball;
BufferedImage background;
public WannaBeStreetFighter() {
try {
ryu = ImageIO.read(new URL("http://www.sirlin.net/storage/street_fighter/ryu_hadoken_pose.png?__SQUARESPACE_CACHEVERSION=1226531909576"));
background = ImageIO.read(new URL("http://fightingstreet.com/folders/variousinfofolder/ehondasbath/hondasfz3stage.gif"));
fireball = ImageIO.read(new URL("http://farm6.staticflickr.com/5480/12297371495_ec19ded155_o.png"));
} catch (IOException ex) {
Logger.getLogger(WannaBeStreetFighter.class.getName()).log(Level.SEVERE, null, ex);
}
fireBalls = new LinkedList<>();
Timer timer = new Timer(40, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
Iterator<Fireball> it = fireBalls.iterator();
while (it.hasNext()) {
Fireball ball = it.next();
if (ball.x > D_W) {
it.remove();
System.out.println(fireBalls.size());
} else {
ball.x += X_INC;
repaint();
}
}
}
});
timer.start();
InputMap inputMap = getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW);
inputMap.put(KeyStroke.getKeyStroke("SPACE"), "hadouken");
getActionMap().put("hadouken", new AbstractAction() {
#Override
public void actionPerformed(ActionEvent e) {
fireBalls.add(new Fireball(fireball));
}
});
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(background, 0, 0, D_W, D_H, this);
g.drawImage(ryu, 50, 125, 150, 115, this);
for (Fireball ball : fireBalls) {
ball.drawFireball(g);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(D_W, D_H);
}
private class Fireball {
Image fireball;
int x = 150;
int y = 125;
public Fireball(Image image) {
fireball = image;
}
public void drawFireball(Graphics g) {
g.drawImage(fireball, x, y, 75, 50, null);
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame("Best Street Fighter ever");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new WannaBeStreetFighter());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}

Did you try creating another instance of Image? For example:
Image fireball, firball2;
Now you have two Image objects. So when you call g.dawImage(...):
public void paint(Graphics g){
g.drawImage(fireball, x, y, null);
g.drawImage(fireball2, x+10, y+10, null);//just draw this other Image
}
You can just keep drawing multiple images. Also, I changed the x and y positions, so the two Image objects don't overlap.
Now if you want a bunch of Image objects, then use an ArrayList of Image objects:
ArrayList<Image> fireballs = new ArrayList<Image>();
Read more about ArrayList here.

Related

Swing: How to reduce the response time of MouseInputAdapter?

I've made a tool which could allow user to draw on screenshot.Here is my code:
package GUI.Views;
import javax.swing.*;
import javax.swing.event.MouseInputAdapter;
import java.awt.*;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
public class Canvas extends JPanel {
public static int radius = 5;
public class DrawPointListener extends MouseInputAdapter{
private void draw(Point p, int radius){
Graphics2D g2 = (Graphics2D)getGraphics();
int x = (int)p.getX() - radius/2;
int y = (int)p.getY() - radius/2;
g2.fillOval(x, y, radius, radius);
}
#Override
public void mousePressed(MouseEvent e) {
draw(e.getPoint(), radius);
}
#Override
public void mouseDragged(MouseEvent e) {
draw(e.getPoint(), radius);
}
}
private BufferedImage screenShot;
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.drawImage(this.screenShot, 0, 0, null);
}
public Canvas() {
this.setVisible(true);
this.screenShot = getScreenShot();
this.setCursor(new Cursor(Cursor.CROSSHAIR_CURSOR));
DrawPointListener drawListener = new DrawPointListener();
this.addMouseListener(drawListener);
this.addMouseMotionListener(drawListener);
}
private BufferedImage getScreenShot() {
try {
return new Robot().createScreenCapture(new Rectangle(Toolkit.getDefaultToolkit().getScreenSize()));
} catch (AWTException e) {
System.out.println("Error");
return null;
}
}
public static void main(String[] args) {
JFrame f = new JFrame();
Canvas canvas = new Canvas();
f.setUndecorated(true);
f.add(canvas);
f.setExtendedState(JFrame.MAXIMIZED_BOTH);
f.setVisible(true);
}
}
Code worked fine when you try to "draw" on the screen slowly, but when you move your mouse quickly, you would see those points are not consecutive, like this:
I think that because there is a time interval of Listener.How could I improve it?
You can't change the time interval of the listener. You are NOT guaranteed to generate an event for every pixel.
So instead of drawing a single point, you need to draw a line between the current and previous point.
Something like:
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.ArrayList;
import javax.swing.JFrame;
import javax.swing.JPanel;
class DrawingPanel extends JPanel
{
private ArrayList<ArrayList<Point>> previous = new ArrayList<ArrayList<Point>>();
private ArrayList<Point> current = new ArrayList<Point>();
private BasicStroke basicStroke;
public DrawingPanel(int strokeSize)
{
basicStroke = new BasicStroke(strokeSize, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND);
MouseAdapter ma = new MouseAdapter()
{
#Override
public void mousePressed(MouseEvent e)
{
current.add( new Point(e.getX(), e.getY()) );
}
#Override
public void mouseDragged(MouseEvent e)
{
current.add( new Point(e.getX(), e.getY()) );
repaint();
}
#Override
public void mouseReleased(MouseEvent e)
{
if (current.size() > 1)
{
previous.add( current );
}
current = new ArrayList<Point>();
}
};
addMouseMotionListener( ma );
addMouseListener( ma );
}
#Override
protected void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setStroke( basicStroke );
// Paint lines from previous drags
for (int i = 0; i < previous.size(); i++)
{
drawLines(g, previous.get(i));
}
// Paint line from current drag
drawLines(g, current);
}
private void drawLines(Graphics g, ArrayList<Point> points)
{
for (int i = 0; i < points.size() - 1; i++)
{
int x = (int) points.get(i).getX();
int y = (int) points.get(i).getY();
int x2 = (int) points.get(i + 1).getX();
int y2 = (int) points.get(i + 1).getY();
g.drawLine(x, y, x2, y2);
}
}
private static void createAndShowGUI()
{
JFrame frame = new JFrame("Drawing Panel");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new DrawingPanel(15));
frame.setSize(400, 400);
frame.setLocationByPlatform( true );
frame.setVisible( true );
}
public static void main(String[] args) throws Exception
{
EventQueue.invokeLater( () -> createAndShowGUI() );
/*
EventQueue.invokeLater(new Runnable()
{
public void run()
{
createAndShowGUI();
}
});
*/
}
}

Creating endless number of objects in JPanel and draw them through PaintComponent in Java

I have one dilemma , how to realize application. I have JPanel with width 288 and height 512, then I created two objects ( images ) and drew them through paintComponent using coordinates
drawImage (Image1,288,128,this) ;
drawImage (Image2, 288, 384, this);
. They are decrementing simultaneously in the X axis and when it reaches x = 144 , new (same) images should be drawn at the coordinates β€˜( x = 288 , y = (int)Math.random()* 512 )’ and begin decrement as well as first ones should still decrements. And this process should be endless. Every new objects reaching x = 144 should build new ones . I tried to create ArrayList with adding coordinates in it
ArrayList arrayX = new ArrayList();
arrayX.add(288)
arrayY.add((int) Math.random()* 512 )
and then extract values through
array.get()
But that was unsuccessfully.
I saw video where man did it using JavaScript through the array
var position = []
position = ({
X : 288
Y : 256
})
And then implemented through the loop like this
function draw() {
for (int i = 0; i < position.length; i++ ){
cvs.drawImage(Image1,position[i].x , position[i].y)
cvs.drawImage(Image2,position[i].x , position[i].y + 50)
position [i] .x - -;
if(position[i].x == 128)
position.push({
X : 288
Y : Math.floor(Math.random()*512 })
})
}
}
I don’t know how to do this in Java.
May be I should use array too to keep variables with coordinates , or arraylist but in different way. Help me please .
Thanks in advance
Conceptually the idea is simple enough, the problem is, Swing is signal thread and NOT thread safe.
See Concurrency in Swing for more details.
This means you can run a long running or blocking operation (like a never ending loop) inside the Event Dispatching Thread, but also, you shouldn't update the UI (or properties the UI depends on) from outside the context of the EDT.
While there are a number of possible solutions to the problem, the simplest is probably to use a Swing Timer, which provides a means to schedule a delay safely (that won't block the EDT) and which will trigger it's updates within the context of the EDT, allowing you to update the UI from within it.
See How to Use Swing Timers for more details.
Now, because you're in a OO language, you should leverage the power it provides, to me, this means encapsulation.
You have a image, you want drawn at a specific location, but whose location change be changed based on some rules, this just screams Plain Old Java Old (POJO)
Normally, I'd start with a interface to describe the basic properties and operations, but for brevity, I've jumped straight for a class...
public class Drawable {
private int x, y;
private Color color;
public Drawable(int x, int y, Color color) {
this.x = x;
this.y = y;
this.color = color;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public Color getColor() {
return color;
}
public void update() {
x--;
if (x <= 144) {
reset();
}
}
protected void reset() {
x = 288;
y = (int) (Math.random() * (512 - 20));
}
public void paint(Graphics2D g2d) {
Graphics2D copy = (Graphics2D) g2d.create();
copy.translate(getX(), getY());
copy.setColor(getColor());
copy.drawOval(0, 0, 20, 20);
copy.dispose();
}
}
But wait, you say, it's using Color instead of image!? Yes, I didn't have any small images at hand, besides, I need to leave you something to do ;)
Now, the animation is a sequence of updating and painting repeatedly until a desired state is reached.
In this case, you don't care about the end state so much, so you can just keep it running.
The "update" cycle is handled by a Swing Timer, which loops over a List of Drawable objects, calls their update methods and then schedules a repaint, which triggers the JPanels paintComponent where by the Drawable objects are painted, simple 😝...
public class TestPane extends JPanel {
private List<Drawable> drawables;
public TestPane() {
drawables = new ArrayList<>(2);
drawables.add(new Drawable(288, 128, Color.RED));
drawables.add(new Drawable(288, 384, Color.RED));
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (Drawable drawable : drawables) {
drawable.update();
}
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(288, 512);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Drawable drawable : drawables) {
Graphics2D g2d = (Graphics2D) g.create();
drawable.paint(g2d);
g2d.dispose();
}
}
}
Putting it altogether - runnable example...
import java.awt.Color;
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.util.ArrayList;
import java.util.List;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new TestPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
public class Drawable {
private int x, y;
private Color color;
public Drawable(int x, int y, Color color) {
this.x = x;
this.y = y;
this.color = color;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public Color getColor() {
return color;
}
public void update() {
x--;
if (x <= 144) {
reset();
}
}
protected void reset() {
x = 288;
y = (int) (Math.random() * (512 - 20));
}
public void paint(Graphics2D g2d) {
Graphics2D copy = (Graphics2D) g2d.create();
copy.translate(getX(), getY());
copy.setColor(getColor());
copy.drawOval(0, 0, 20, 20);
copy.dispose();
}
}
public class TestPane extends JPanel {
private List<Drawable> drawables;
public TestPane() {
drawables = new ArrayList<>(2);
drawables.add(new Drawable(288, 128, Color.RED));
drawables.add(new Drawable(288, 384, Color.RED));
Timer timer = new Timer(5, new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
for (Drawable drawable : drawables) {
drawable.update();
}
repaint();
}
});
timer.start();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(288, 512);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Drawable drawable : drawables) {
Graphics2D g2d = (Graphics2D) g.create();
drawable.paint(g2d);
g2d.dispose();
}
}
}
}
"Is there a simpler solution"? Yes, of course, I always go to the hardest possible way to solve a problem first πŸ€ͺ. First, good animation is hard. Seriously. I've been playing around with this sought of thing more nearly 20 years, making a good animation engine which is flexible to meet all the possible needs it might be put to is near impossible mission, especially in a framework which isn't really designed for it.
If you don't belief me, you could have a look at
How do I change Swing Timer Delay inside actionPerformed()
How can I fade out or fade in by command JPanel, its components and its color
which are just a couple of examples how complicated animation can be
Sorry, you'd be amazed how often I get asked "can it be simpler" when it comes to animation ;)
"Every new objects reaching x = 144 should build new ones
So, apparently I may be confused about this particular point. If this means "adding new objects after reaching 144" then this raises some new issues. The primary issue is one over GC overhead, which cause slow downs in the animation. Sure, we're only dealing with about 4-6 objects, but it's one of those things which can come back to byte you if you're not careful.
So I took the above example and made some modifications to the update cycle. This adds a reusePool where old objects are placed and can be re-used, reducing the GC overhead of repeatedly creating and destroying short lived objects.
The decaying List simply ensures that once an object passes the swanPoint, it won't be consider for re-spawning new objects. Sure you could put a flag on the POJO itself, but I don't think this is part of the POJOs responsibility
public TestPane() {
drawables = new ArrayList<>(2);
reusePool = new ArrayList<>(2);
decaying = new ArrayList<>(2);
timer = new Timer(5, new ActionListener() {
private List<Drawable> spawned = new ArrayList<>(5);
#Override
public void actionPerformed(ActionEvent e) {
spawned.clear();
Iterator<Drawable> it = drawables.iterator();
int swapnPoint = getWidth() / 2;
while (it.hasNext()) {
Drawable drawable = it.next();
drawable.update();
if (drawable.getX() > 0 && drawable.getX() < swapnPoint) {
if (!decaying.contains(drawable)) {
decaying.add(drawable);
Drawable newDrawable = null;
if (reusePool.isEmpty()) {
newDrawable = new Drawable(
getWidth() - 20,
randomVerticalPosition(),
randomColor());
} else {
newDrawable = reusePool.remove(0);
newDrawable.reset(getWidth() - 20,
randomVerticalPosition(),
randomColor());
}
spawned.add(newDrawable);
}
} else if (drawable.getX() <= -20) {
System.out.println("Pop");
it.remove();
decaying.remove(drawable);
reusePool.add(drawable);
}
}
drawables.addAll(spawned);
repaint();
}
});
}
This will now allow objects to travel the whole width of the width, spawning new objects as they pass the half way point. Once they pass beyond the visual range of the view, they will be placed into the reuse List so they can be reused again when new objects are required.
Runnable example...
import java.awt.Color;
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.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.Timer;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
public class Test {
public static void main(String[] args) {
new Test();
}
public Test() {
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
ex.printStackTrace();
}
TestPane testPane = new TestPane();
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(testPane);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
EventQueue.invokeLater(new Runnable() {
#Override
public void run() {
testPane.start();
}
});
}
});
}
public class Drawable {
private int x, y;
private Color color;
public Drawable(int x, int y, Color color) {
this.x = x;
this.y = y;
this.color = color;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
public Color getColor() {
return color;
}
public void update() {
x--;
}
protected void reset(int x, int y, Color color) {
this.x = x;
this.y = y;
this.color = color;
}
public void paint(Graphics2D g2d) {
Graphics2D copy = (Graphics2D) g2d.create();
copy.translate(getX(), getY());
copy.setColor(getColor());
copy.fillOval(0, 0, 20, 20);
copy.setColor(Color.BLACK);
copy.drawOval(0, 0, 20, 20);
copy.dispose();
}
}
public class TestPane extends JPanel {
private List<Drawable> drawables;
private List<Drawable> decaying;
private List<Drawable> reusePool;
private Color[] colors = new Color[]{Color.BLUE, Color.CYAN, Color.DARK_GRAY, Color.GREEN, Color.MAGENTA, Color.ORANGE, Color.PINK, Color.RED, Color.YELLOW};
private Random rnd = new Random();
private Timer timer;
public TestPane() {
drawables = new ArrayList<>(2);
reusePool = new ArrayList<>(2);
decaying = new ArrayList<>(2);
timer = new Timer(40, new ActionListener() {
private List<Drawable> spawned = new ArrayList<>(5);
#Override
public void actionPerformed(ActionEvent e) {
spawned.clear();
Iterator<Drawable> it = drawables.iterator();
int swapnPoint = getWidth() / 2;
while (it.hasNext()) {
Drawable drawable = it.next();
drawable.update();
if (drawable.getX() > 0 && drawable.getX() < swapnPoint) {
if (!decaying.contains(drawable)) {
decaying.add(drawable);
Drawable newDrawable = null;
if (reusePool.isEmpty()) {
System.out.println("New");
newDrawable = new Drawable(
getWidth() - 20,
randomVerticalPosition(),
randomColor());
} else {
System.out.println("Reuse");
newDrawable = reusePool.remove(0);
newDrawable.reset(getWidth() - 20,
randomVerticalPosition(),
randomColor());
}
spawned.add(newDrawable);
}
} else if (drawable.getX() <= -20) {
System.out.println("Pop");
it.remove();
decaying.remove(drawable);
reusePool.add(drawable);
}
}
drawables.addAll(spawned);
repaint();
}
});
}
public void start() {
drawables.add(new Drawable(getWidth(), 128, randomColor()));
drawables.add(new Drawable(getWidth(), 384, randomColor()));
timer.start();
}
protected int randomVerticalPosition() {
return rnd.nextInt(getHeight() - 20);
}
protected Color randomColor() {
return colors[rnd.nextInt(colors.length - 1)];
}
#Override
public Dimension getPreferredSize() {
return new Dimension(288, 512);
}
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Drawable drawable : drawables) {
Graphics2D g2d = (Graphics2D) g.create();
drawable.paint(g2d);
g2d.dispose();
}
}
}
}
My answer is completely based on MadProgrammer's answer (A comprehensive tutorial actually).
From what I read in the post : "Every new objects reaching x = 144 should build new ones", I think the desired implementation is slightly different:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class ImageAnimator {
public ImageAnimator() {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new AnimationPane());
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
public static class Drawable {
private int x;
private final int y;
private static final Image image = image();
//construct with a random y value
public Drawable(int x) {
this(x, -1);
}
public Drawable(int x, int y) {
this.x = x;
this.y = y < 0 ? (int) (Math.random() * (512 - 20)) : y;
}
public int getX() { return x; }
public int getY() { return y; }
public void update() { x--; }
public Image getImage(){ return image; }
public static Image image() {
URL url = null;
try {
//5.SEP.2021 replaced dead link
//url = new URL("https://dl1.cbsistatic.com/i/r/2017/09/24/b2320b25-27f3-4059-938c-9ee4d4e5cadf/thumbnail/32x32/707de8365496c85e90c975cec8278ff5/iconimg241979.png");
url = new URL("https://cdn3.iconfinder.com/data/icons/softwaredemo/PNG/32x32/Circle_Green.png");
return ImageIO.read(url);
} catch ( IOException ex) {
ex.printStackTrace();
return null;
}
}
}
public class AnimationPane extends JPanel {
private final List<Drawable> drawables;
private static final int W = 288, H = 512, CYCLE_TIME = 5;
public AnimationPane() {
drawables = new ArrayList<>(2);
drawables.add(new Drawable(W, H/4));
drawables.add(new Drawable(W, 3*H/4));
Timer timer = new Timer(CYCLE_TIME, e -> animate());
timer.start();
}
private void animate() {
for (Drawable drawable : new ArrayList<>(drawables)) {
drawable.update();
if(drawable.getX() == W/2) {
drawables.add(new Drawable(W)); //random Y
}
if(drawable.getX() <= 0) {
drawables.remove(drawable);
}
}
repaint();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(W, H);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Drawable drawable : drawables ) {
g.drawImage(drawable.getImage(),drawable.getX(), drawable.getY(), null);
}
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(()->new ImageAnimator());
}
}
The following solution is based on my previous answer.
I add it in response to MadProgrammer's comment: "A better solution is to pool the objects for re-use".
DrawAblesProducer produces drawable objects on-demand. It also stores surplus object, to prevent producing too many such objects.
I post it as a separate answer because the additional functionality comes with somewhat higher complexity:
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class ImageAnimator {
private static final int W = 288, H = 512;
public ImageAnimator() {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Testing");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(new AnimationPane(new DrawAblesProducer()));
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
});
}
public class AnimationPane extends JPanel {
private final List<Drawable> drawables;
private static final int CYCLE_TIME = 5;
private final DrawAblesProducer producer;
public AnimationPane(DrawAblesProducer producer) {
this.producer = producer;
drawables = new ArrayList<>(2);
drawables.add(producer.issue(W, H/4));
drawables.add(producer.issue(W, 3*H/4));
Timer timer = new Timer(CYCLE_TIME, e -> animate());
timer.start();
}
private void animate() {
for (Drawable drawable : new ArrayList<>(drawables)) {
drawable.update();
if(drawable.getX() == W/2) {
drawables.add(producer.issue(W)); //random Y
}else if(drawable.getX() <= 0) {
drawables.remove(drawable);
producer.retrn(drawable);
}
}
repaint();
}
#Override
public Dimension getPreferredSize() {
return new Dimension(W, H);
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Drawable drawable : drawables ) {
g.drawImage(drawable.getImage(),drawable.getX(), drawable.getY(), null);
}
}
}
//produces `drawable` objects on-demand. stores surplus object, to prevent producing
//too many such objects
public class DrawAblesProducer {
private final Queue<Drawable> warehouse = new LinkedList<>();
public Drawable issue(int x){
return issue(x, -1);
}
public Drawable issue(int x, int y){
Drawable drawable = warehouse.poll();
if(drawable != null ) {
drawable.setX(x); drawable.setY(y);
return drawable;
}
return new Drawable(x, y);
}
public void retrn(Drawable drawable){
warehouse.add(drawable);
}
}
public static class Drawable {
//made static so image is reused for all instances
private static final Image image = image();
private int x, y;
//construct with a random y value
public Drawable(int x) {
this(x, -1);
}
public Drawable(int x, int y) {
setX(x);
setY(y);
}
public int getX() { return x; }
public void setX(int x) { this.x = x;}
public int getY() { return y; }
public void setY(int y) {
this.y = y < 0 ? randomY() : y ;
}
private int randomY() {
int iHeight = image.getHeight(null);
return iHeight + (int) (Math.random() * (H - iHeight));
}
public void update() { x--; }
public Image getImage(){ return image; }
public static Image image() {
URL url = null;
try {
//5.SEP.2021 replaced dead link
//url = new URL("https://dl1.cbsistatic.com/i/r/2017/09/24/b2320b25-27f3-4059-938c-9ee4d4e5cadf/thumbnail/32x32/707de8365496c85e90c975cec8278ff5/iconimg241979.png");
url = new URL("https://cdn3.iconfinder.com/data/icons/softwaredemo/PNG/32x32/Circle_Green.png");
return ImageIO.read(url);
} catch ( IOException ex) { ex.printStackTrace(); }
return null;
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(()->new ImageAnimator());
}
}

Swing JPanel - Drawn graphics duplicate instead of moving

For some reason, my KeyListener works just fine and fires off the Booleans to make down and up true and false and the y value changes according to those Booleans exactly how I want it to. My problem is that for some reason, the red rectangle appears to grow in size rather than move, and I'm pretty sure that it's because the previous frame is not cleared. I tried to use super.paintComponent(g); to clear the frame but this accomplishes nothing. Here's the code:
JFrame:
import java.awt.*;
import javax.swing.*;
public class H extends JFrame
{
public H()
{
super("Atlas Blade");
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setResizable(false);
pack();
P p = new P();
Insets frameInsets = getInsets();
int frameWidth = p.getWidth() +
(frameInsets.left + frameInsets.right);
int frameHeight = p.getHeight() + (
frameInsets.top + frameInsets.bottom);
setPreferredSize(new Dimension(frameWidth, frameHeight));
setLayout(null);
add(p);
pack();
setVisible(true);
}
}
JPanel:
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseEvent;
import java.awt.image.*;
public class P extends JPanel implements KeyListener, Runnable
{
private long updateCount=0;
private long paintCount=0;
private int updatesPerSecond = 50;
private boolean aLeft,aRight,aDown,aUp=false;
private boolean up,down,left,right=false;
int x = 20;
int y=20;
Hb box = new Hb(x,y);
Rectangle rect = new Rectangle(0,300,300,50);
BufferedImage buffer;
public P()
{
super();
setSize(600,350);
//setSize(50,50);
buffer = new BufferedImage (600,350,BufferedImage.TYPE_4BYTE_ABGR);
addKeyListener(this);
Thread jim = new Thread(this);
jim.start();
}
public void run()
{
int waitToUpdate = 1000/updatesPerSecond;
long startTime = System.nanoTime();
while(true)
{
boolean shouldRepaint = false;
long currentTime = System.nanoTime();
long updatesNeeded = (((currentTime-startTime) / 1000000))/ waitToUpdate;
for(long x = updateCount; x< updatesNeeded; x++)
{
updateGame();
shouldRepaint=true;
updateCount++;
}
if(shouldRepaint)
{
paintCount++;
repaint();
}
try
{
Thread.sleep(5);
}
catch(Exception e)
{
e.printStackTrace();
}
}
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics bg = buffer.getGraphics();
bg.setColor(Color.BLACK);
bg.drawRect(0,300,300,50);
bg.setColor(Color.RED);
bg.fillRect(x,y,35,35);
g.drawImage(buffer,0,0,null);
}
public void updateGame()
{
box.updateHitbox(x,y);
if(down)
{
if(!box.center.intersects(rect))
{
y++;
//y=y+40;
}
}
else if(up)
{
if(!box.center.intersects(rect))
{
y--;
}
}
}
public void keyPressed(KeyEvent e)
{
int code = e.getKeyCode();
if(code==KeyEvent.VK_A)
{
left=true;
right=false;
aLeft=true;
aRight=false;
aDown=false;
aUp=false;
}
if(code==KeyEvent.VK_D)
{
left=false;
right=true;
aLeft=false;
aRight=true;
aDown=false;
aUp=false;
}
if(code==KeyEvent.VK_S)
{
System.out.println(y);
down=true;
up=false;
aLeft=false;
aRight=false;
aDown=true;
aUp=false;
}
if(code==KeyEvent.VK_W)
{
down=false;
up=true;
aLeft=false;
aRight=false;
aDown=false;
aUp=true;
}
repaint();
}
public void keyTyped(KeyEvent e)
{
}
public void keyReleased(KeyEvent e)
{
int code = e.getKeyCode();
if(code==e.VK_A)
{
left=false;
aLeft=false;
}
if(code==e.VK_D)
{
right=false;
aRight=false;
}
if(code==e.VK_S)
{
down=false;
aDown=false;
}
if(code==e.VK_W)
{
up=false;
aUp=false;
}
}
public void addNotify()
{
// call super so the method still does what it was built to do
super.addNotify();
// requests focus so that key inputs are sent to this screen
requestFocus();
}
}
And the Hb class:
import java.awt.Rectangle;
public class Hb
{
public Rectangle center,left,right,up,down;
public Hb(int x, int y)
{
center = new Rectangle(x,y,50,50);
left = new Rectangle(x-1,y+1,1,48);
right = new Rectangle(x+50,y+1,1,48);
up = new Rectangle(x+1,y-1,48,1);
down = new Rectangle(x+1,y+50,48,1);
}
public void updateHitbox(int x, int y)
{
center = new Rectangle(x,y,50,50);
left = new Rectangle(x-1,y+1,1,48);
right = new Rectangle(x+50,y+1,1,48);
up = new Rectangle(x+1,y-1,48,1);
down = new Rectangle(x+1,y+50,48,1);
}
}
Your problem is that you're doing all your drawing in the BufferedImage, and that doesn't allow erasure of "dirty" pixels. Instead, only draw in the BufferedImage that which should be a static and unchanging part of the image, usually the background. The foreground image that moves should be painted directly in paintComponent using the Graphcis object given to the method by the JVM.
public P() {
super();
setSize(600, 350); // not recommended
buffer = new BufferedImage(600, 350, BufferedImage.TYPE_4BYTE_ABGR);
Graphics bg = buffer.getGraphics();
bg.setColor(Color.BLACK);
bg.drawRect(0, 300, 300, 50);
bg.dispose();
// ....
}
and
#Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(buffer, 0, 0, null);
g.setColor(Color.RED);
g.fillRect(x, y, 35, 35);
}

drawImage error when trying to draw a sprite onto a JFrame

I have this really crappy sprite sheet that I made, which is basically just a bunch of circles and ovals so I can grasp Sprite animation.
import javax.imageio.ImageIO;
import javax.swing.*;
import javax.swing.Timer;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.*;
public class CircleSprite extends JFrame implements ActionListener, Runnable{
BufferedImage circles;
BufferedImage[] test;
Timer timer;
int cycle = 0;
Graphics g = getGraphics();
public void asd(){
setSize(500,500);
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
try {
circles = ImageIO.read(new File("CircleTest.png"));
} catch (IOException e) {
e.printStackTrace();
}
final int width = 206;
final int height = 206;
final int rows= 2;
final int columns = 3;
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
test = new BufferedImage[rows * columns];
try{
for(int i = 0; i < rows; i++)
for(int j = 0;j<columns;j++)
{
test[i*columns + j] = circles.getSubimage(j * width, i * height, width, height);
}
}catch(Exception e){
e.printStackTrace();
}
timer = new Timer(500, this);
setVisible(true);
}
public void actionPerformed(ActionEvent e){
//0, 1, 2, 3, 4, 5, 4, 3, 2, 1, 0, 1, 2, etc.
repaint();
g.drawImage(test[cycle], 25, 25, null);
if(cycle >= 5){
cycle--;
}
if(cycle <=0){
cycle++;
}
}
public void run(){
asd();
while(timer.isRunning() == false && this.isVisible() == true){
timer.start();
try {
CircleSpriteRun.t1.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
The error occurs here: g.drawImage(test[cycle], 25, 25, null);
At first I though it had to do with the ImageObserver being null, and looking further into it, I was wrong. Now, I think it might be because of the timer, but I don't really know too much about Timers, let alone the swing one.
This all runs on a Thread being executed in another class, and it could also have to do with the while statement in the run method, since that also involves the timer.
Since you didn't provide a runnable example, I created one to show how to properly code a Swing application.
First, you must start a Swing application with the SwingUtilities.invokeLater method. Here's how I started the CircleSprite class.
public static void main(String[] args) {
SwingUtilities.invokeLater(new CircleSprite());
}
Second, you should use a JPanel for drawing, not a JFrame. Here's the DrawingPanel I created. My version of CircleSprite draws a circle in a random location every 2 seconds.
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = -4603711384104715819L;
private int x;
private int y;
private BufferedImage image;
public DrawingPanel(BufferedImage image) {
this.image = image;
this.x = 0;
this.y = 0;
setPreferredSize(new Dimension(500, 500));
}
public void setPoint(int x, int y) {
this.x = x;
this.y = y;
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, x, y, null);
}
}
Third, you create the Swing GUI before you do anything with the Swing GUI. Here's the run method from the CircleSprite class. I create the GUI, then I start the thread that does the random drawing.
public void run() {
circle = createCircle();
frame = new JFrame("Circle Sprite");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
drawingPanel = new DrawingPanel(circle);
frame.add(drawingPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
new Thread(new RandomDraw(drawingPanel)).start();
}
Fourth, you only extend a Swing component when you want to override a method, like I did in the DraawingPanel class. You use Swing Components otherwise.
Here's the entire, runnable, CircleSprite class. You can use this as a model for future Swing applications.
package com.ggl.testing;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.util.Random;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
public class CircleSprite implements Runnable {
private BufferedImage circle;
private DrawingPanel drawingPanel;
private JFrame frame;
#Override
public void run() {
circle = createCircle();
frame = new JFrame("Circle Sprite");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
drawingPanel = new DrawingPanel(circle);
frame.add(drawingPanel);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
new Thread(new RandomDraw(drawingPanel)).start();
}
private BufferedImage createCircle() {
BufferedImage image = new BufferedImage(100, 100,
BufferedImage.TYPE_INT_RGB);
Graphics g = image.getGraphics();
g.setColor(Color.WHITE);
g.fillRect(0, 0, 100, 100);
g.setColor(Color.BLUE);
g.fillOval(10, 10, 80, 80);
g.dispose();
return image;
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new CircleSprite());
}
public class DrawingPanel extends JPanel {
private static final long serialVersionUID = -4603711384104715819L;
private int x;
private int y;
private BufferedImage image;
public DrawingPanel(BufferedImage image) {
this.image = image;
this.x = 0;
this.y = 0;
setPreferredSize(new Dimension(500, 500));
}
public void setPoint(int x, int y) {
this.x = x;
this.y = y;
repaint();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(image, x, y, null);
}
}
public class RandomDraw implements Runnable {
private DrawingPanel drawingPanel;
private Random random;
public RandomDraw(DrawingPanel drawingPanel) {
this.drawingPanel = drawingPanel;
this.random = new Random();
}
#Override
public void run() {
while (true) {
sleep();
int x = random.nextInt(400);
int y = random.nextInt(400);
drawingPanel.setPoint(x, y);
}
}
private void sleep() {
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
}
}
}
}

animated background java game

I was programming a game similar to asteroid, but I do not understand how to spawn the asteroids in the background.
now i spawn an asteroid in the main class but i want create a class for the asteroid ho i do it?
MAIN CLASS
public void paintComponent(Graphics g)
{
super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
if(flag_img)
{
background(g2d);
logo(g2d);
menu(g2d);
spaceship(g2d);
crediti(g2d);
}
}
background function(now)
private void background(Graphics2D g2d)
{
asteroidi_g_x+=r.nextInt(4);
asteroidi_g_y+=r.nextInt(1);
g2d.drawImage(asteroidi_g[0], asteroidi_g_x,asteroidi_g_y,this);
}
background function(what i want)
private void background(Graphics2D g2d)
{
asteroid asteroid = new asteroid[10];
}
and class asteroid
public class asteroid extends JPanel implements ActionListener
{
private BufferedImage images_asteroid;
private boolean flag_img;
private JPanel jp;
private int x,y;
public asteroide_grande(JPanel jp)
{
flag_img = true;
x = (jp.getWidth()/2);
y = (jp.getHeight()/2);
this.jp = jp;
try {
images_asterod = ImageIO.read(this.getClass().getResource("images/asteroid/a1.png"));
} catch(IOException e){flag = false;}
}
#Override
public void paintComponent(Graphics g)
{
super.paintComponent(g);
if(flag_img)
{
g.drawImage(images_asteroid, 100, 100,this);
}
}
#Override
public void actionPerformed(ActionEvent e)
{
x=x-1;
y=y+1;
repaint();
}
method paintcomponent in class doesn't work
Don't have your Asteroid class extends JPanel. Instead have it as a class that model's asteroid data and has data manipulation methods. You'll also want to have a draw method that take a Graphic context. Something like
public class Asteroid {
Image asteroidImage;
JPanel panel;
int x, y;
public Asteroid(JPanel panel, Image image, int x, int y) {
this.panel = panel;
this.asteroidImage = image;
this.x = x;
this.y = y;
}
public void drawAsteroid(Graphics g) {
g.drawImage(asteroidImage, x, y, panel);
}
public void move() {
x += 5;
}
}
Now you have a model of an asteroid, you can create a List of Asteriod objects and iterate through them and use it's drawAsteroid method to paint them. Something like
public class GamePanel extends JPanel {
List<Asteroid> asteroids;
Image asteroidImage;
public GamePanel(){
asteroidImage = ...
asteroids = new ArrayList<>();
asteroids.add(new Asteroid(GamePanel.this, asteroidImage, 100, 100));
// add more asteriods
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
for (Asteriod asteroid: asteriods) {
asteriod.drawAsteroid(g);
}
}
}
To animate them, you'll want to use a javax.swing.Timer. See more at How to Use Swing Timers. You'll want to manipulate the Asteriod data in the Timer. With the code provided above, you can just call it's move method, then call repaint(). Something like
public GamePanel(){
...
Timer timer = new Timer(30, new ActionListener(){
public void actionPerformed(ActionEvent e) {
Iterator it = asteroids.iterator();
while (it.hasNaext()) {
Asteroid asteriod = (Asteroid)it.next();
asteroid.move();
}
}
});
}
You can see a bunch more complete example of animating multiple objects here and here and here and here and here
Here's a full example. You'll see I included a Rectangle2D object in the Astreroid class. That's just if you want to check for collision detection. You should move the Rectangle2D x and/or y with every Asreroid movement of x and y. Then you can check if asteroid.rectangle.intersects(someOtherObject)
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.imageio.ImageIO;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
import javax.swing.Timer;
public class AsteroidBackground extends JPanel {
private static final int D_W = 400;
private static final int D_H = 600;
BufferedImage asteroidImage;
BufferedImage background;
List<Asteroid> asteroids;
Random random = new Random();
int countToAddAsteroid = 0;
int y;
public AsteroidBackground() {
try {
asteroidImage = ImageIO.read(getClass().getResource("/resources/small-asteroid.png"));
background = ImageIO.read(getClass().getResource("/resources/space.png"));
} catch (IOException ex) {
Logger.getLogger(AsteroidBackground.class.getName()).log(Level.SEVERE, null, ex);
}
asteroids = new ArrayList<>();
y = 0 - asteroidImage.getHeight();
Timer timer = new Timer(40, new ActionListener(){
public void actionPerformed(ActionEvent e) {
if (countToAddAsteroid >= 25) {
int randX = random.nextInt(D_W);
asteroids.add(new Asteroid(AsteroidBackground.this, asteroidImage, randX, y));
countToAddAsteroid = 0;
}
countToAddAsteroid++;
Iterator it = asteroids.iterator();
while (it.hasNext()) {
Asteroid asteroid = (Asteroid)it.next();
if (asteroid.y >= D_H) {
it.remove();
} else {
asteroid.move();
}
}
repaint();
}
});
timer.start();
}
#Override
protected void paintComponent(Graphics g) {
super.paintComponent(g);
g.drawImage(background, 0, 0, this);
for (Asteroid asteroid : asteroids) {
asteroid.drawAsteroid(g);
}
}
#Override
public Dimension getPreferredSize() {
return new Dimension(D_W, D_H);
}
public class Asteroid {
Rectangle2D rectangle;
Image asteroidImage;
JPanel panel;
int x, y;
public Asteroid(JPanel panel, Image image, int x, int y) {
this.panel = panel;
this.asteroidImage = image;
this.x = x;
this.y = y;
rectangle = new Rectangle2D.Double(
x, y, image.getWidth(panel), image.getHeight(panel));
}
public void drawAsteroid(Graphics g) {
g.drawImage(asteroidImage, x, y, panel);
}
public void move() {
y += 5;
}
}
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame frame = new JFrame();
frame.add(new AsteroidBackground());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setLocationRelativeTo(null);
frame.setVisible(true);
}
});
}
}

Categories

Resources