Spritesheet problem

Hello, I’ve got a problem with a spritesheet I’m trying to make using Java 2D. I first load an external PNG into a BufferedImage using the ImageIO class and using a Sprite class I pass in the buffered spritesheet, where I want it in the frame (x, y), the top left hand point of the tile from the spritesheet that I want to extract (xOffset, yOffset) and the width and height of the tile.

The external PNG is just a spritesheet containing the characters a-z, 0-9 that I made in photoshop, with each tile being 20x20 pixels. I’ll post the code so you can see exactly what I mean, but what the problem is, is that when a new character is drawn using the Component classes paint method, the previously rendered characters disappear.


import java.awt.BorderLayout;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JFrame;

public class Main
{
	public static final int WIDTH = 300;
	public static final int HEIGHT = 300;
	
	private Screen screen;
	
	public Main()
	{
		JFrame frame = new JFrame();
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setLayout(new BorderLayout());
		frame.setSize(WIDTH, HEIGHT);
		frame.setLocationRelativeTo(null);
		frame.setResizable(false);
		frame.setVisible(true);
		
		BufferedImage ss = null;
		try
		{
			ss = ImageIO.read(new File("charSprite.png"));
		}
		catch(IOException e)
		{
			e.printStackTrace();
		}
		
		screen = new Screen(WIDTH, HEIGHT);
		screen.loadImage(new Sprite(ss, 0, 0, 20, 20).getSprite(), 20, 20);
		screen.loadImage(new Sprite(ss, 0, 0, 20, 20).getSprite(), 40, 40);
		
		frame.add(screen);
	}
	
	public static void main(String[] args)
	{
		new Main();
	}
}

Here’s the Sprite class, for dissecting the tile from the spritesheet:


import java.awt.image.BufferedImage;

public class Sprite
{
	private BufferedImage sprite;
	
	public Sprite(BufferedImage ss, int xOffset, int yOffset, int width, int height)
	{
		sprite = ss.getSubimage(xOffset, yOffset, width, height);
	}
	
	public BufferedImage getSprite()
	{
		return sprite;
	}
}

Here’s the screen class which holds the bufferedimage containing each element within the screen:


import java.awt.Component;
import java.awt.Graphics;
import java.awt.image.BufferedImage;

public class Screen extends Component
{
	private BufferedImage display = null;
	private int[] pixels;
	
	public Screen(int w, int h)
	{
		display = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
		pixels = new int[w * h];
	}
	
	public void loadImage(BufferedImage im, int x, int y)
	{
		int[] p = im.getRGB(0, 0, im.getWidth(), im.getHeight(), null, 0, im.getWidth());
		for(int y1 = 0; y1 < im.getHeight(); y1++)
		{
			for(int x1 = 0; x1 < im.getWidth(); x1++)
			{
				pixels[(y1 + y) * display.getWidth() + (x1 + x)] = p[y1 * im.getWidth() + x1];
			}
		}
		display.setRGB(0, 0, display.getWidth(), display.getHeight(), pixels, 0, display.getWidth());
			
		repaint();
	}
	
	public void paint(Graphics g)
	{
		g.drawImage(display, 0, 0, display.getWidth(), display.getHeight(), null);
	}
}

I’m quite new to this and just trying to understand how swing works so any help would be appreciated. Thanks!

Paul

JFrame’s ContentPane’s default layout is BorderLayout, which uses 1 component for the center, and 1 component for each of the 4 sides. Calling JFrame.add(Component) adds it to the center component. Any further calls will replace the previous one. What you need is 1 component that you draw everything to.

Thanks for the reply. Yeah, I’ve just thought to add a screen class to keep a pixels by pixels reference of everything on the screen, then I can just pass in bufferedimages, extract their pixel arrays and then overlay the new image within a nested loop in the correct position.

I’ve updated my original post now to what I currently have and it seems to work perfectly, except the images only seem to appear roughly 50% of the time once the program is ran. If I minimize the program and maximize it again the images appear, though. Any idea what could be causing that?

Could be because you’re only adding the Screen there once, you’d need to add a loop of somekind, than handle your paint method in there so it’s constantly repainting.

I think it stays as it is until you clear the pixel array and update the screen buffer. I’ll have to create a clear buffer method or something to clear that out. I think the problem lies around here in the main class:


ss = ImageIO.read(new File("charSprite.png"));

As when the pictures don’t render, they both don’t and likewise when they do. So that would suggest that it was a problem with the spritesheet I’m feeding into the sprite classes. Not really sure about a solution though.

What I would do, and what I do do is:
Take the spritesheet, chop it down into proper frames, give it a pinkish background color (if you want it to be transparent), load it through transparency and ignore those pink pixels.
Thought i’ve never tried grabbing a subImage of a image…

You could make your Screen class extend a JPanel, than add the JPanel to the frame, make your Main class “extends Screen”.
You could do your Screen class like this:


import java.awt.Component;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.awt.Graphics2D;
import java.awt.RenderingHints;

public class Screen extends JPanel // Updated
{
   private BufferedImage display = null;
   private int[] pixels;
   
   public Screen(int w, int h)
   {
      display = new BufferedImage(w, h, BufferedImage.TYPE_INT_ARGB);
      pixels = new int[w * h];
   }
   
   public void loadImage(BufferedImage im, int x, int y)
   {
      int[] p = im.getRGB(0, 0, im.getWidth(), im.getHeight(), null, 0, im.getWidth());
      for(int y1 = 0; y1 < im.getHeight(); y1++)
      {
         for(int x1 = 0; x1 < im.getWidth(); x1++)
         {
            pixels[(y1 + y) * display.getWidth() + (x1 + x)] = p[y1 * im.getWidth() + x1];
         }
      }
      display.setRGB(0, 0, display.getWidth(), display.getHeight(), pixels, 0, display.getWidth());
         
      repaint();
   }
   
	public void cycle() { // Updated
		repaint();
	}

   public void paint(Graphics g) // Updated
   {
		super.paint(g);
		final Graphics2D g2d = (Graphics2D) g;
		g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
		g2d.setRenderingHint(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY);
		g2d.drawImage(display, 0, 0, display.getWidth(), display.getHeight(), null);
   }
}

And your Main class like this:


import java.awt.BorderLayout;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;

import javax.imageio.ImageIO;
import javax.swing.JFrame;

public class Main extends Screen implements Runnable { //Updated
   public static final int WIDTH = 300;
   public static final int HEIGHT = 300;
   
   private Screen screen;
   
   public Main() {
      JFrame frame = new JFrame();
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.setLayout(new BorderLayout());
      frame.setSize(WIDTH, HEIGHT);
      frame.setLocationRelativeTo(null);
      frame.setResizable(false);
      BufferedImage ss = null;
      try {
         ss = ImageIO.read(new File("charSprite.png"));
      } catch(IOException e) {
         e.printStackTrace();
      }
      screen = new Screen(WIDTH, HEIGHT);
      screen.loadImage(new Sprite(ss, 0, 0, 20, 20).getSprite(), 20, 20);
      screen.loadImage(new Sprite(ss, 0, 0, 20, 20).getSprite(), 40, 40);
      frame.add(new Screen());
      frame.setVisible(true); // Just realized you were setting your screen to visible, THAN you were adding everything.
   }
   
   public static void main(String[] args) {
      new Main();
   }

	@Override
	public void run() { //Updated
		super.cycle();
	}
}

So your Main class is automaticly calling that “cycle” method from your Screen class which is than telling it to “repaint”, which was changed to “super.paint(g)”, which would paint your GUI/Main screen.

Hope it helps some mate (Would need proper imports/changes made).

How foolish of me. :] Just moved the setvisible below the add call and now it’s rendering in time. Cheers both of you for your help!

Np mate, i’m fairly newish.
Need any help drop me a pm.