Hello. I’ve only been learning java a little less than a year now. I’m working on a 2d rpg game in which I have a sprite sheet that contains all of the images for my main characters sprite. (the sprite sheet looks something like this: http://sdb.drshnaps.com/sheets/Misc/Misc/Other/SBM4-Bomberman-.gif ). However, I’m having trouble figuring out how to extract each image singularly. I know it would involve finding the exact box of pixels that surround the desired image, cutting it out, and loading it into the program. Is there a class in the api that can do this? If so, can it also make the color surrounding the extracted image transparent? On the other hand, am I taking a wrong approach to making a 2d game? Is there a better way to load a main character with animation? I appreciate any help on this matter.
[quote=“ryuzaki,post:1,topic:30084”]
In your paint method, you need to use something like:
//g is your Graphics Object
//sw and sh are the width and height of sprite
//px and py are the position of your sprite in screen
//i and j are the coordinates of the sprite in the sheet that you want to paint
g.drawImage(sheet, px,py, px+sw,py+sh, i*sw, j*sh, (i+1)*sw, (j+1)*sh, null);
[quote=“ryuzaki,post:1,topic:30084”]
For this you need to use an image editor software to make the bg color of the prote sheet transparent.
Rafael.-
Thank you very much! As soon as I determine the pixel coordinates of the images I need I’ll try your technique.
Hmm…I’m having trouble getting it to work. I was testing it on the sprite sheet that I posted above, the coordinates being where the sprite is facing down. Here’s some of the code (pretty much what you gave me) that I used to test it:
import java.awt.*;
import javax.swing.*;
public class Bomberman extends JPanel {
// Load image
Image sheet = Toolkit.getDefaultToolkit().getImage("bomberman.gif");
// Width and height of sprite
int sw = 15;
int sh = 30;
// Position of sprite on screen
int px = 100;
int py = 100;
// Coordinates of desired sprite image
int i = 0;
int j = 125;
public Bomberman() {
}
// Draw image
public void paint(Graphics g) {
super.paintComponent(g);
g.drawImage(sheet, px,py, px+sw,py+sh, i*sw, j*sh, (i+1)*sw, (j+1)*sh, null);
}
}
I have another class which has a main method to display the image on a panel. Don’t worry, I know the class works because I tested it with just a single image and it displayed it correctly (just making sure I rule out any possible errors). Any suggestions as to what I’m missing? I appreciate your help!
hello!
I think you missed the MediaTracker for your gif pic. It brings on all of the pic data within a required time you cannot know. Plus it is recommended to impl the paintComponent meth for all swing component in place of std paint. And moreover the heivyweight oomp like the JPanel don’t require to call the super.paint, but mainly to redraw entirely in the paintComponent meth as you must clear the clipping rect.
Thus I recommend you to add the MediaTracker and to clearRect th graphics before to draw the GIf.
The problem is in the coordinates of the sprite, maybe I didn’t explain it well.
If you have the next sheet:
ABCDEFG HIJKLMN OPQRSTU
And you want to display the sprite in the ‘F’ position, your coordinates are i = 5, j = 0.
For the ‘K’ sprite i = 3 and j = 1; and ‘A’ is in (0,0).
Also, as said by broumbroum(entish??), you are using the Toolkit to get your image but aren’t waiting for it to load. There is a new way to load your images using ImageIO.
//Change this
Image sheet = Toolkit.getDefaultToolkit().getImage("bomberman.gif");
//With this
Image sheet = ImageIO.read(new File("bomberman.gif"));
You need to import the ImageIO and File classes and catch some Exceptions, but this is better to get your image loaded into your app.
Rafael.-
Thank you for your input. My program now reads images using ImageIO. I also realized that my x and y coordinates were switched (i should equal 125 and j should equal 0). Anyway, I’m using photoshop and found the exact pixels needed for the sprite, so the new coordinates are more accurate. However, the drawImage line of code rdcarvallo gave me still didn’t work as expected. When I made i = 0 and j = 0 with the height = 100 and width = 100, it would display what one would expect: the first 100 x 100 pixels of the image. But when I tried it with the sprite’s exact pixels it wouldn’t display anything. After searching around the internet I came across the setClip method which seems pretty useful:
setClip(int x, int y, int width, int height)
Sets the current clip to the rectangle specified by the given coordinates.
Using this method I was able to display the specific sprite that I wanted. Here is the code so far:
import java.awt.*;
import javax.swing.*;
import java.io.*;
import javax.imageio.*;
class Bomberman extends JPanel {
// Load image
Image sheet = ImageIO.read(new File("bomberman.gif"));
// Width and height of sprite
int sw = 15;
int sh = 23;
// Position of sprite on screen
int px = 100;
int py = 100;
// Coordinates of desired sprite image
int i = 124;
int j = 2;
public Bomberman() throws Exception {
}
// Draw image
public void paint(Graphics g) {
super.paintComponent(g);
g.setClip(i, j, sw, sh);
g.drawImage(sheet, i - 124,j - 2, null);
}
}
It cuts out everything around the clipped part of the image, leaving just the desired sprite to be displayed. Now the part that I’m not sure with now is how to set the location of the image. The sprite is positioned where it would be as if the rest of the image was there. So, it’s located 124 pixels to the right and 2 pixels down. The problem is, how would I set it to a different position on the screen? I think once this problem is solved I’ll be able to figure out the rest for my program. Any suggestions?
There are half a zillion ways to do that and setClip isn’t really one of the fastest, I’m afraid.
IIRC the last time we benched it over here… copying individual frames over to their own BufferedImages results in the fastest rendering. Especially if the sprite sheet exceeds 2^16 (eg 256x256) pixels. How you copy it over doesn’t really matter since it’s only done once.
Btw using an equally spaced sheet (each cell has the same size) is a lot easier to handle. If the sizes vary a lot you can use sprite packing tools to create a sheet and some text file (=atlas), which keeps track which sprite is stored where.
Hmm. I understand what you are saying. I don’t know of any way to copy the sprites location and store it as its own image though…can you give an example of how you would do it? Or the kind of process it would take?
For the copy process… you create a compatible BufferedImage in the desired size, get the Graphics object, set the composite mode to AlphaComposite.Src (this allows you to overwrite it with the alpha, too - instead of using the alpha to blend), draw the image, and finally you dispose the Graphics object. Now you have a freshly created image which only contains this single frame.
Here is a simple demo:
import java.io.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.image.*;
import javax.imageio.*;
import javax.swing.*;
public class SpriteSlice extends Canvas{
GraphicsConfiguration gc = GraphicsEnvironment.getLocalGraphicsEnvironment().getDefaultScreenDevice().getDefaultConfiguration();
BufferStrategy strat;
static int []keys = new int[0xFF];
static int []prevKeys = new int[0xFF];
BufferedImage [][]cards;
int W=300;
int H=200;
int cardNum=11; //11=queen
int cardType=1; //1=hearts
public SpriteSlice(){
BufferedImage img=null;
try{
img=ImageIO.read(new BufferedInputStream(getClass().getResourceAsStream("/cards.gif")));
}catch(IOException e){
e.printStackTrace();
System.exit(1);
}
cards=slice(img,13,4);//13 rows, 4 columns
setSize(W,H);
JFrame f=new JFrame("SpriteSlice");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.add(this);
f.pack();
f.setResizable(false);
f.setVisible(true);
createBufferStrategy(2);
strat=getBufferStrategy();
enableEvents(AWTEvent.KEY_EVENT_MASK);
requestFocusInWindow();
gameLoop();
}
public void gameLoop(){
while(true){
//### tick
if(down(KeyEvent.VK_RIGHT))
cardNum++;
if(down(KeyEvent.VK_LEFT))
cardNum--;
if(down(KeyEvent.VK_DOWN))
cardType++;
if(down(KeyEvent.VK_UP))
cardType--;
cardNum%=cards.length;
if(cardNum<0)
cardNum+=cards.length;
cardType%=cards[0].length;
if(cardType<0)
cardType+=cards[0].length;
//### draw
Graphics g=strat.getDrawGraphics();
g.setColor(new Color(0x778899));
g.fillRect(0,0,W,H);
g.setColor(new Color(0xFFFFFF));
g.drawString("cards["+cardNum+"]["+cardType+"]",10,10);
g.drawString("(use the cursor keys to change)",10,25);
/*
for(int i=0;i<4;i++){
g.drawString(""+keys[KeyEvent.VK_LEFT+i],10+10*i,180);
g.drawString(""+prevKeys[KeyEvent.VK_LEFT+i],10+10*i,195);
}*/
drawCentered(g,cards[cardNum][cardType]);
g.dispose();
strat.show();
}
}
private void drawCentered(Graphics g, BufferedImage img){
g.drawImage(img,W/2-img.getWidth()/2,H/2-img.getHeight()/2,null);
}
private BufferedImage[][] slice(BufferedImage source, int cols, int rows){
int w=source.getWidth();
int h=source.getHeight();
int spritew=w/cols;
int spriteh=h/rows;
BufferedImage [][]slices=new BufferedImage[cols][rows];
for(int x=0;x<cols;x++){
for(int y=0;y<rows;y++){
slices[x][y]=gc.createCompatibleImage(spritew,spriteh,source.getTransparency());
Graphics2D g=(Graphics2D)slices[x][y].getGraphics();
g.setComposite(AlphaComposite.Src);
g.drawImage(
source,
0,
0,
spritew,
spriteh,
w/cols*x,
h/rows*y,
w/cols*x+spritew,
h/rows*y+spriteh,
null
);
g.dispose();
}
}
return slices;
}
private boolean down(int key){
boolean ret=false;
if(keys[key]==1&&prevKeys[key]==0) //currently down, previously up
ret=true;
prevKeys[key]=keys[key];
return ret;
}
public void processKeyEvent(KeyEvent ke){
keys[ke.getKeyCode()&0xFF]=(ke.getID()==KeyEvent.KEY_RELEASED)?0:1;
}
public static void main(String[]args){
new SpriteSlice();
}
}
The used image:
http://kaioa.com/k/cards.gif
I think I noticed one problem with ryuzaki’s implementation of the original way he was told to get the sprite images from the sprite sheet. Unless I’m mistaken, the sprite coordinates to access the sheet shouldn’t be measured in pixels but function more like indices into a 2d array. The sprite width/height will be in pixels. This would explain why having (0,0) as your sprite coordinates work, since (0,0) in pixels is still the same as (0,0) as array indices. If want the next image over, just increment whichever coordinate by 1, instead of the actual amount of pixels that it’s over by.
lhkbob, I believe you are right! Using the
g.drawImage(sheet, px,py, px+sw,py+sh, isw, jsh, (i+1)*sw, (j+1)*sh, null);
implementation, when I set i = 1 and j = 0, it displays the first sprite properly! One problem with this method though is that the sprites aren’t lined up evenly with the 2d array…for example when i = 8 and j = 0, it shows half of one sprite and half of another. This could probably be solved if I knew exactly where the array lined up and redid the sheet accordingly…
oNyx, thank you for the elaborate example! I think I understand what kind of method you used. When I get more time I’ll be able to look at it more thoroughly. I think the technique of loading the images separately prior to the painting process is the most practical method, so I will probably end up using this one. Thanks everyone for the help! 8)