I am making an RPG with a tilemap. To generate the tilemap i loop through a 2 dimensional array but that means that when I repaint I have to do that each time. If I repaint too much the screen flickers how could I stop this.
package sexyCyborgFromAnOtherDimension;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Timer;
import java.util.TimerTask;
import javax.swing.JPanel;
#SuppressWarnings("serial")
public class Game extends JPanel
{
KeyLis listener;
int mapX = 20;
int mapY = 20;
boolean up = false;
boolean down = false;
boolean right = false;
boolean left = false;
String[][] map;
public Game()
{
super();
try
{
map = load("/maps/map1.txt");
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
listener = new KeyLis();
this.setFocusable(true);
this.requestFocus();
this.addKeyListener(listener);
Timer timer = new Timer();
TimerTask task = new TimerTask()
{
#Override
public void run()
{
if(up)
{
mapY++;
repaint();
}
if(down)
{
mapY--;
repaint();
}
if(right)
{
mapX--;
repaint();
}
if(left)
{
mapX++;
repaint();
}
}
};
timer.scheduleAtFixedRate(task, 0, 10);
}
public void paint(Graphics g)
{
super.paintComponent(g);
setDoubleBuffered(true);
Graphics2D g2 = (Graphics2D)g;
g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
for (int x = 0; x < map.length; x++)
{
for (int y = 0; y < map[x].length; y++)
{
switch(map[x][y])
{
case "0":
g.setColor(Color.GREEN);
break;
case "1":
g.setColor(Color.GRAY);
break;
}
g.fillRect(y*20+mapX, x*20+mapY, 20, 20);
}
}
g.setColor(Color.BLACK);
g.fillRect(400, 400, 20, 20);
}
String[][] load(String file) throws IOException
{
BufferedReader br = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream(file)));
int lines = 1;
int length = br.readLine().split(" ").length;
while (br.readLine() != null) lines++;
br.close();
BufferedReader br1 = new BufferedReader(new InputStreamReader(getClass().getResourceAsStream(file)));
String[][] map = new String[lines][length];
for (int i = 0; i < lines; i++)
{
String line = br1.readLine();
String[] parts = line.split(" ");
for (int y = 0; y < length; y++)
{
map[i][y] = parts[y];
}
}
br1.close();
return map;
}
private class KeyLis extends KeyAdapter
{
#Override
public void keyPressed(KeyEvent e)
{
switch (e.getKeyCode())
{
case KeyEvent.VK_UP:
up = true;
break;
case KeyEvent.VK_DOWN:
down = true;
break;
case KeyEvent.VK_LEFT:
left = true;
break;
case KeyEvent.VK_RIGHT:
right = true;
break;
}
}
#Override
public void keyReleased(KeyEvent e)
{
switch (e.getKeyCode())
{
case KeyEvent.VK_UP:
up = false;
break;
case KeyEvent.VK_DOWN:
down = false;
break;
case KeyEvent.VK_LEFT:
left = false;
break;
case KeyEvent.VK_RIGHT:
right = false;
break;
}
}
}
}
Thank you for your help
Edit
Using javax.swing.Timer removes all flickering even with a 10 ms delay.
A number of small things jump out at me.
Firstly. You might be better using javax.swing.Timer instead of java.util.Timer, this will at least allow the events to flow a little better.
Secondly, 10 milliseconds is to short a time period, seriously, you don't need 100fps, 60fps is about 17 milliseconds, I normally use 40 milliseconds for 25fps. This might give the EDT some breathing room to actually respond to the repaint requests.
Thirdly, you should be using paintComponent instead of paint. It's low enough in the call chain to guaranteed to be double buffered
Fourthly, you should avoid calling any method that might reschedule a repaint (like setDoubleBuffered for example, to this in the constructor if you must, but, Swing components are double buffered by default)
Fifthly, where possible, paint all "static" or slow changing content to a backing buffer and paint that instead. This will increase the speed at which paint can work as it doesn't get stuck in a lot of small loops.
You may want to take a look at Painting in AWT and Swing for more details about the paint process
Some additional examples...
Swing animation running extremely slow - was able to go from 500 elements up to 4500 elements moving on the screen at one time - talks about resource management as well
Java Bouncing Ball
How to make line animation smoother?
the images are not loading
And because kleo scares me (and it's also a good idea), you should also take a look at How to use Key Bindings, which will solve your focus issues with the KeyListener
You have set double buffering on the panel, but the things you are drawing on it yourself are not being double buffered. Calling setDoubleBuffered(true) only applies buffering for child components of the container.
You need manually buffer stuff you draw yourself.
I would suggest moving away from a JPanel and use a Canvas instead. Have a look at how a Canvas is used with a buffer strategy in this class, where it can handle hundreds of things being drawn on screen with perfect smoothness and no flickering (the game runs on a 10ms sleep loop too).
Not really sure if it solves your problem, but you can consider to use a 1dimensional array.
when int width; is the width of your screen/tile/whatever you loop and int height is the height you coud do the following:
instead of, what you do:
for(int x = 0; x < width; x++)
{
for(int y = 0; y < height; y++)
{
// dosomething with your array
myarray[x][y];
}
}
you could go through this way
for(int y = 0; y < height; y++)
{
for(int x = 0; x < width; x++)
{
// dosomething with your array
myarray[x + y * width];
}
}
Related
Over the past week or so I've been working on a game, where you're a white square that tries to dodge red squares which fall from the top of the screen. You can only move left and right, there are only 3 available positions to move to (left, middle, right), and the red square begins to fall faster as the game progresses. When I move the player (white square) everything repaints just fine, but when the red square's position updates it doesn't repaint at all. Say the red square moves once every second; if I wait one second, the repaint doesn't do anything. However, if I wait one second (or any amount of time) and then move the player, both squares jump to their new positions at once. After looking through the code for an hour, I still can't find any issues with it, and it doesn't help that I'm very new to coding, which probably means it's a really obvious mistake.
import java.util.*;
import javax.swing.*;
import java.awt.*;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import javax.swing.JComponent;
import javax.swing.JFrame;
public class boxface extends JComponent implements KeyListener {
private static int x=0, y=0;
static int counter = 0;
static Thread t = new Thread();
public void keyPressed(KeyEvent e) {
if(e.getKeyCode()== KeyEvent.VK_RIGHT)
moveRight();
else if(e.getKeyCode()== KeyEvent.VK_LEFT)
moveLeft(); }
public void keyReleased(KeyEvent e) {}
public void keyTyped(KeyEvent e) {}
static Rectangle player = new Rectangle(x, 400, 50, 50);
Rectangle bg = new Rectangle(0, 0, 700, 750);
static int x2 = ((int)((Math.random()*3)))*50;
static Rectangle enemy = new Rectangle(x2, y, 50, 50);
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
g2.setColor(Color.BLACK);
g2.fill(bg);
g2.setColor(Color.WHITE);
g2.fill(player);
g2.setColor(Color.RED);
g2.fill(enemy); }
public void moveLeft() {
if(x > 0) {
x -= 50;
player.setLocation(x, 400);
repaint();}}
public void moveRight() {
if(x < 100) {
x += 50;
player.setLocation(x, 400);
repaint();} }
public void enemyDown() {
if(enemy.getBounds().y == 400) {
x2=((int)((Math.random()*3)))*50;
y=0;
enemy.setLocation(x2, y);
counter++;
repaint(); }
else {
y = y + 50;
enemy.setLocation(x2, y);
repaint();}}
public boxface(){
addKeyListener(this);
setFocusable(true);
setFocusTraversalKeysEnabled(false); }
public static void main(String[] args) {
javax.swing.SwingUtilities.invokeLater(new Runnable() {
public void run() {
JFrame f = new JFrame();
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setBounds(400, 200, 156, 479);
f.setMinimumSize(new Dimension(156, 0));
f.setResizable(false);
f.getContentPane().add(new boxface());
f.setVisible(true); }
});
boxface exec = new boxface();
int t = 20;
for(long i = 0; i < 21; i++) {
if(i == t) {
exec.enemyDown();
i = 0; }
if(counter == 10) t = 15;
if(counter == 25) t = 10;
if(counter == 50) t = 7;
if(counter == 100) t = 5;
if(counter == 150) t = 3;
if(counter == 225) t = 1;
if(enemy.intersects(player))
break;
System.out.println(i); }
}
}
Swing uses a "passive rendering" algorithm. This means that the UI will be updated at irregular intervals based on decisions been made by the RepaintManager
See Painting in AWT and Swing for more details
What you need is some way to schedule regular updates. Before I discuss that, some basic game theory...
Game/Main Loop...
Most games have a concept of a "main loop". The "main loop" does a number of jobs, it:
Updates the state of the game. This includes:
Updating the position of the player based on the current state of the input (keyboard/mouse/joystick)
Updating the position of other objects/obstacles within the world
Collision detection
Other states which need to be updated before they can be rendered
Rendering the current state
While you could use a Thread to accomplish this, Swing is NOT thread safe, meaning, you should never update the state of the UI (or any state that the UI relies on) outside the context of the Event Dispatching Thread. Swing is also single threaded, so you can't simply use Thread.sleep or some other loop within the context of the Event Dispatching Thread either.
The simplest solution would be to use a Swing Timer. This is pseudo loop, which is called repeatedly after a specified delay. Each time the timer is triggered, you'd perform the "main loop" operations and call repaint, thus, schedule a (mostly) regular update to the UI.
See How to use Swing Timers for more details
Other considerations...
Because the state of the game is actually updated within the "main loop", you can no longer respond to key events directly. Instead, you need to set a series of flags indicating the current state of your input triggers (ie isAPressed at its simplest form).
In most cases, this would invoke a Set and predefined series of inputs. I like to use a enum as it clearly defines the accepted inputs (up/down/left/right etc...). When pressed, you add the appropriate input to the Set and on release, you remove it. It deals with any repeated states and removes the oddity of the delay between the first press and the repeated key events.
Talking about key events. KeyListener has known issues, while there are "hacks" to "work around" it, they are just that, "hacks".
The recommend method for monitoring for (limited) key input is to use the key bindings API
Hi I am developing a game that a fighter moves right and left and shoots. For the shooting part, I tried to use a for loop to slow the speed down and user can see the bullet. But it wasn't enough. I used sleep too but not a good answer. Now I have no idea what to do.
Here is my paintComponent calss:
package game;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import javax.imageio.ImageIO;
import javax.swing.JPanel;
public class PaintComponent extends JPanel implements KeyListener {
int dx = 200-15;
int dy = 450;
int my = 450;
ArrayList<Bullet> bullets = new ArrayList<>();
public Rectangle2D rec =new Rectangle2D.Double(dx , dy, 30, 10);
Rectangle2D recB = new Rectangle2D.Double(dx+13 , my, 6, 6);
// public Polygon pol = new Polygon
private BufferedImage imageBg, imageFi, imageBu;
public PaintComponent() {
this.addKeyListener(this);
this.setFocusable(true);
this.setBackground(Color.white);
try {
imageBg = ImageIO.read(new File("C:\\Java\\NetbeansProjects\\Game\\bg.jpg"));
imageBu = ImageIO.read(new File("C:\\Java\\NetbeansProjects\\Game\\bul.png"));
imageFi = ImageIO.read(new File("C:\\Java\\NetbeansProjects\\Game\\fi.png"));
} catch (IOException ex) {
System.out.println("No background image is available!");
}
}
public void shoot(){
if(bullets != null){
for(int i=0; i<bullets.size(); i++){
for(int j=0; j<200; j++){
bullets.get(i).setdy(my-j);
}
System.out.println(bullets.get(i).getdy());
}
}
}
public void moveRec(KeyEvent evt){
switch(evt.getKeyCode()){
case KeyEvent.VK_LEFT:
dx -= 5;
rec.setRect(dx, dy, 30, 10);
recB.setRect(dx+13, dy, 6, 6);
repaint();
break;
case KeyEvent.VK_RIGHT:
dx += 5;
rec.setRect(dx, dy, 30, 10);
recB.setRect(dx+13, dy, 6, 6);
repaint();
break;
case KeyEvent.VK_SPACE:
Bullet b = new Bullet(dx, dy);
bullets.add(b);
shoot();
break;
}
}
#Override
public void paintComponent(Graphics grphcs)
{super.paintComponent(grphcs);
Graphics2D gr = (Graphics2D) grphcs;
int X = (int) rec.getCenterX();
int Y = (int) rec.getCenterY();
gr.drawImage(imageBg, 0, 0, null);
gr.drawImage(imageFi, X-50, Y-75, null);
gr.setColor(Color.GRAY);
if(bullets != null){
for(int i=0;i<bullets.size();i++){
gr.drawImage(imageBu, null, bullets.get(i).getdx(), bullets.get(i).getdy());
repaint();
}
}
gr.draw(recB);
}
#Override
public void keyTyped(KeyEvent e) {
}
#Override
public void keyPressed(KeyEvent e)
{moveRec(e);}
#Override
public void keyReleased(KeyEvent e)
{}
}
and this is my bullet calss:
package game;
public class Bullet {
private int x, y, newy;
public Bullet(int x, int y){
this.x = x;
this.y = y;
}
public int getdy(){
return y;
}
public void setdy(int newy){
y = newy;
}
public int getdx(){
return x;
}
}
I think you are going wrong with slowing down the loop. The last thing you want to do is slow down the game loop or sleep the game loop. This will affect all you objects with in the game.
There are multiple way to go about this:
Smaller increments per tick
One of the most obvious things you could do is make the increment of the bullet smaller. Lets take a look at your shoot(); method:
public void shoot(){
if(bullets != null){
for(int i=0; i<bullets.size(); i++){
for(int j=0; j<200; j++){
bullets.get(i).setdy(my-j);
}
System.out.println(bullets.get(i).getdy());
}
}
}
As far as i understand you are iterating 200x over all the bullets, each tick the bullet's y axis gets changed, using the formula "my - j" or "450 - the tick index"
In order to slow down the bullet you would need to divide the j with a certain number to get the desired speed of the bullet. For instance: "my - (j / 2)" would impact the speed of the bullet. Try to play around with these variables to get the desired speed.
Adding a speed modifier
What a lot of games to is a speed modifier or a base speed for each projectile. This could be of use to you, the only thing i noticed that you are kinda trying to simulate loss of velocity. To achieve this result you would need another variable. Let call that "time to live" for right now.
So if we modify the bullet class it would look like this. Noticed we also have a new function called Move();. This will calculate the next move based upon the variables.
public class Bullet {
private int x, y, newy;
private speed, ttl; //ttl = time to live
public Bullet(int x, int y, int speed){
this.x = x;
this.y = y;
this.speed= speed;
this.ttl = 250;
}
public int Move()
{
//Do some calculation to perform loss of velocity within a reasonable range. Because these number might be overkill
this.speed -= (ttl / 100);
y += this.speed;
ttl--;
}
public int getdy(){
return y;
}
public void setdy(int newy){
y = newy;
}
public int getdx(){
return x;
}
}
What the code now does is it calculates the speed based upon the time to live variable the longer the bullet live the less velocity it will have. Adjusting the speed variable makes you able to control the bullet better. And to say so myself it looks a lot more neater in the shoot method:
public void shoot(){
if(bullets != null){
for(int i=0; i<bullets.size(); i++){
bullets.get(i).Move();
}
}
}
Of course there is more to it, like checking if the speed and time to live dont go out of bounds and stuff, but i think your smart enough to figure that out ;)
Running it off a timer
As ControlAltDel said you can implement a timer, im not an expert on java so im not going in depth on this. But it surely it is a possibility. Its nothing more then implement the current shoot method inside the tick function of the timer. Of course removing the for i<200 loop. Since its not very effecient.
Anyways
If i did get something wrong or misunderstood (or even grammer mistaked :) ) the problem, im sorry. If there are any question i loved to hear from you ;).
And please not that this is untested code and im only here to explain things you could try to get it working a intended!
Sincerly,
Syntasu.
UPDATE:
Some explaining on how to update the bullet's.
In order to update the bullets we need to make it run off a loop. Since in this case the main loop is were also where the drawing is happening, the "paintComponent" method. There is already a loop withing the paint component to draw the bullet, only thing we have to do is to add our logic regarding the .Move(); method.
The paint component will look as following ( + i also fixed the tabbing ):
#Override
public void paintComponent(Graphics grphcs)
{
super.paintComponent(grphcs);
Graphics2D gr = (Graphics2D) grphcs;
int X = (int) rec.getCenterX();
int Y = (int) rec.getCenterY();
gr.drawImage(imageBg, 0, 0, null);
gr.drawImage(imageFi, X-50, Y-75, null);
gr.setColor(Color.GRAY);
if(bullets != null)
{
for(int i=0;i<bullets.size();i++)
{
//Here is were we loop over the bullet list, lets add the move method
bullets.get(i).Move();
gr.drawImage(imageBu, null, bullets.get(i).getdx(), bullets.get(i).getdy());
repaint();
}
}
gr.draw(recB);
}
The thing is added is "bullets.get(i).Move();". This will now run every frame. This will work in theory (inb4 im not testing these codes). Going by the assumption you use multiple instance's of the bullet class, each class should encapsulate their own speed and time to live variable.
Implementing this will make the shoot method obsolete. What you can do is move the code inside the paintComponent that is related to shooting and move that to the shoot function.
Regarding the time to live variable, i would like to add one more piece to the code. This will take care of garbage collection of bullets. Since now they live indefinitly:
for(int i=0;i<bullets.size();i++)
{
Bullet b = bullets.get(i);
if(b.ttl >= 1)
{
bullets.get(i).Move();
gr.drawImage(imageBu, null, b.getdx(), b.getdy());
}
else
{
//Remove the bullet from the list
//In C# its bullets.Remove(b);
}
repaint();
}
Hopefully this resolves the issue of the bullet not moving. And potential performance issue due the bullets not being destroyed. In before, it there are any questions i love to hear them! ;)
Finally I did it with adding a timer in my bullet class and repaint() in my paintcomponent method!
package game;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.Timer;
public class Bullet {
private int x, y;
private int speed, ttl;
public final Timer timer1;
public Bullet(int x, int y, int speed){
this.x = x;
this.y = y;
this.speed= speed;
this.ttl = 250;
ActionListener actListener = new ActionListener() {
#Override
public void actionPerformed(ActionEvent e) {
Move();
}
};
this.timer1 = new Timer(50, actListener);
timer1.start();
}
public int getdy(){
return y;
}
public void setdy(int newy){
y = newy;
}
public int getdx(){
return x;
}
public int Move(){
//Do some calculation to perform loss of velocity within a reasonable range. Because these number might be overkill
this.speed -= (ttl / 100);
y += this.speed;
ttl--;
return y;
}
}
In your game loop make it update the bullet every so many ticks. You most likely never want to use sleep to slow down something in a game as it will need a new thread or it will sleep the whole game.
If you do not know what a game loop is, a game loop is basically a loop which continually takes input, updates the game (such as a bullet), renders everything, then pauses the entire program (you can use sleep) for the amount of time which the loop has left over from what is expected. You also want to update the game according to how many ticks or milliseconds have passed since the last update. This will prevent the game from running faster or slower on different computers.
I haven't read this all the way but THIS might help.
Also, not sure if this is correct, I think using Canvas instead of a JPanel will be better for making a game. It is what I used for my first java game.
Edit:
If you want to use JPanel and no game loop you can make the shoot method create a new thread and use the sleep to slow it down. Also the way you have the shoot method setup can cause problems if multiple bullets are shot because it will loop through each bullet in two separate loops and if you have a separate thread the bullets array can be modified while it is looping in one thread therefor causing an error.
Try this:
public void shoot(Bullet bullet){
new Thread(new Runnable(){
for(int j=0; j<200; j++){
bullet.setdy(my-j);
try{
Thread.sleep(time);//set the time
catch(Exception e){
e.printStackTrace();
}
}
System.out.println(bullet.getdy());
getBullets().remove(bullet);
}).start();
}
Create a method called getBullets(). Make sure it has the synchronized modifier.
protected synchronized ArrayList<Bullet> getBullets(){
return bullets;
}
This will make sure it can only be modified by one thread at a time.
Finally where the player presses space, change bullets.add(b); to getBullets().add(b);
I'm kind of new to Java and OO programming, here is the code of moving black and white balls problem. First let me explain the program that I want in the output: there are n balls(for example 6 balls) on the window, one black and one white, in each move we only are allowed to move just one ball and this movement should be shown on the screen, and at the end all the white balls should be on one side and all the black balls should be on the other side. Here is an example of six balls:
I have written the program and it seems working good and no flaws in the algorithm, but my problem is that I can't show animation of the movement of the balls, in each movement one ball should swap its place with its neighbor ball, but all I get is the final arrangements of the balls. Please someone help me with the animation part. I would be really thankful for that.
code:
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Ellipse2D;
import javax.swing.*;
public class DrawPanel extends JPanel implements ActionListener
{
Timer myTimer = new Timer(2000, this);
public static final int NUMBER_OF_CIRCLES = 10; //number of circles which are to moved
static int[] circles = new int[NUMBER_OF_CIRCLES];
public void paintComponent(Graphics g)
{
int x = 0; //start point of circles;
int length = 40; //diagonal of the circles
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
Ellipse2D circle;
//painting n circles based on the array
for(int index = 0; index<10; index++)
{
if(circles[index] == 0){ //if the element of the arrayy is 0 then draw a void circle
circle = new Ellipse2D.Double(x, 120, length, length);
g2.draw(circle);
}
else if(circles[index] == 1){ //if the element of the array is 1 them draw a filled circle
circle = new Ellipse2D.Double(x, 120, length, length);
g2.fill(circle);
}
x += 45; //increas start pont of the next circle 45 pixles
}
myTimer.start();
}
public void actionPerformed(ActionEvent e)
{
int tmp; //template for swaping elements
int condition; //condition of the forS
arrayFill(circles); //fills the array based on the writen method, one 1 and one 0 like: 0 1 0 1 0 1 0 1
//here is the part which works good, it changes palces of an elemen at time.
//at the end of this part the array would be like: 1 1 1 1 0 0 0 0
if(NUMBER_OF_CIRCLES % 2 == 0)
condition = circles.length/2 -1;
else
condition = circles.length/2;
for(int i = circles.length-1, k = 1; i>condition; i--, k++)
{
for(int j = i - k; j<i ;j++)
{
tmp = circles[j];
circles[j] = circles[j+1];
circles[j+1] = tmp;
//if we call arrayPrint method it will print the array but I don't know why repaint is not working here
//arrayPrint(circles);
repaint();
}
}
}
//fills the array, one 1 and one 0. Example: 0 1 0 1 0 1 0 1 0 1
public static void arrayFill(int[] array)
{
for(int i = 0; i<array.length; i++)
{
if( i%2 == 0)
array[i] = 0;
else
array[i] = 1;
}
}
}//end of class
And the main Class:
import javax.swing.JFrame;
public class BlackAndWhiteBallsMoving {
public static void main(String[] args)
{
DrawPanel myPanel = new DrawPanel();
JFrame myFrame = new JFrame();
myFrame.add(myPanel);
myFrame.setSize(600, 500);
myFrame.setTitle("Black And White Balls Moving");
myFrame.setVisible(true);
myFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
}
}//end of class
The events triggered by the Timer are performed on the same event thread as the repaints. Calling repaint does not actively perform a paint event, rather it queues one for later. When you call your repaints from within the timer event, they will only get executed once the timer event is completed.
What you need to do is refactor your loop so that only a single swap is performed each time the timer triggers. I've done this for you as an example:
public class DrawPanel extends JPanel implements ActionListener {
public static final int NUMBER_OF_CIRCLES = 10;
Timer myTimer = new Timer(500, this);
int[] circles = new int[NUMBER_OF_CIRCLES];
public DrawPanel() {
arrayFill(circles);
if(NUMBER_OF_CIRCLES % 2 == 0) {
condition = circles.length/2 -1;
} else {
condition = circles.length/2;
}
i = circles.length - 1;
k = 1;
myTimer.start();
}
int i, j, k;
int condition;
boolean outer = true;
#Override
public void actionPerformed(ActionEvent e) {
if(outer) {
if(i > condition) {
j = i - k; // set j
outer = false; // and move to the inner loop swap
} else {
myTimer.stop(); // the outer loop is done so stop the timer
}
}
if(!outer) {
int tmp = circles[j];
circles[j] = circles[j+1];
circles[j+1] = tmp;
repaint();
j++;
if(j >= i) {
i--;
k++;
outer = true; // move to the outer condition
} // next time the timer triggers
}
}
#Override
protected void paintComponent(Graphics g) {
int x = 0;
int length = 40;
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
Ellipse2D circle;
for(int index = 0; index<10; index++) {
if(circles[index] == 0){
circle = new Ellipse2D.Double(x, 120, length, length);
g2.draw(circle);
} else if(circles[index] == 1){
circle = new Ellipse2D.Double(x, 120, length, length);
g2.fill(circle);
}
x += 45;
}
//myTimer.start();
}
public static void arrayFill(int[] array) {
for(int i = 0; i<array.length; i++) {
if( i%2 == 0) {
array[i] = 0;
} else {
array[i] = 1;
}
}
}
}
(I'm sure it could be factored another way.)
Also:
I added #Override annotations which you should use. Doing so will warn you when you make certain mistakes. (Like misspelling a method name or incorrectly declaring its signature.)
I moved circles to an instance variable because I don't see a reason it should be static. It is part of the state of the DrawPanel instance.
I created a constructor which initializes variables such as circles.
paintComponent is a protected method and it should remain so unless there is a reason to promote it to public.
(I removed your comments and changed the bracing style just to condense the code for my answer.)
As a side note, you should read the tutorial Initial Threads. You are not creating your GUI on the Swing event thread. Basically you need to wrap your code in main inside a call to invokeLater:
public static void main(String[] args) {
SwingUtilities.invokeLater(new Runnable() {
#Override
public void run() {
// create and show your GUI
}
});
}
The basic problem is in your actionPerformed method. Your two for loops are rearranging the array to its final arrangement very quickly. Each loop iteration will take a matter of nanoseconds to milliseconds to complete (it depends on how the repaint() method works). The entire process is finished in less than 50 milliseconds or so. That's too fast for your eyes to keep up.
Basically, the repaint() method is working, but it's working too fast for human eyes to keep up.
If you break up the for loops into something that does one step of the algorithm each time it's called, you can trigger that from a timer and see the animation at a human-detectable speed.
add a paint thread. it should always call repaint() like,
new Thread(){ // this can be started on main or constructor of object
public void run(){
while(true){
repaint();
try {
Thread.sleep(50);
} catch(Exception e){ }
}
}
}.start();
and then, on action performed, mark moving objects like movingObjects, keep a animate_x = 0 and keep a boolean variable like existAnimation
then on paintComponent, increase animate_x
animate_x = animate_x + 1;
if (animate_x >= MAX_WIDTH_OF_ANIMATION){
existAnimation = false;
}
and use this existAnimation, animate_x and movingObjects
like,
public void paintComponent(Graphics g)
{
int x = 0; //start point of circles;
int length = 40; //diagonal of the circles
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
Ellipse2D circle;
//painting n circles based on the array
for(int index = 0; index<10; index++)
{
int paint_x = x;
if (movingObjects.has(circles[index])){
paint_x += animate_x;
}
if(circles[index] == 0){ //if the element of the arrayy is 0 then draw a void circle
circle = new Ellipse2D.Double(paint_x, 120, length, length);
g2.draw(circle);
}
else if(circles[index] == 1){ //if the element of the array is 1 them draw a filled circle
circle = new Ellipse2D.Double(paint_x, 120, length, length);
g2.fill(circle);
}
x += 45; //increas start pont of the next circle 45 pixles
}
myTimer.start();
}
I'm working on a Mario game and am in need of assistance and suggestions on how to go about creating hit detection for a tilemap.
Currently, the player has the ability to walk/jump through the blocks.
I added in a fixed detection to the ground for now which I am hoping to replace with regular hit detection.
I understand that there are four sides to each block and the player. Only some blocks need hit detection and some things you might need to know is that the player stays at 300px(middle of screen) 98% of the time.
The only thing that moves is the map
The map is rendered from a .txt file and is rendered like so:
for(int y=0;y<map.length;y++) {
for(int x=0;x<map[y].length;x++) {
int index = map[y][x];
int yOffset = 0;
if(index>(tileSheet.getWidth() / Engine.TILE_WIDTH) -1) {
yOffset++;
index = index - (tileSheet.getWidth() / Engine.TILE_WIDTH);
}
g.drawImage(tileSheet,
((x * Engine.TILE_WIDTH)*scale)+position,
((y * Engine.TILE_HEIGHT)*scale),
(((x * Engine.TILE_WIDTH) + Engine.TILE_WIDTH )*scale)+position,
(((y * Engine.TILE_HEIGHT) + Engine.TILE_HEIGHT)*scale),
index * Engine.TILE_WIDTH,
yOffset * Engine.TILE_HEIGHT,
(index * Engine.TILE_WIDTH) + Engine.TILE_WIDTH,
(yOffset * Engine.TILE_HEIGHT) + Engine.TILE_HEIGHT,
null
);
}
}
//This code is actually longer(included file later on)
Colour hit detection is too slow and inconsistent for multi coloured tiles
Since the map is moving I suppose I need to move the hit detection boxes with it. As for selecting the boxes that it should detect might be difficult. Maybe it would be a better idea to make the code NOT hit detect certain tiles.
My attempts have ended in obfuscation of code. Can anyone suggest the easiest way to implement the hit detection? (keep in mind I have jumping).
The important codes are listed below:
Board.java(The panel where everything is drawn)
package EvilMario; //Include this class in the EvilMario game package
import java.awt.*; //Imported to allow use of Image
import java.awt.event.*; //Imported to allow use of ActionListener
import javax.swing.*; //Import swing
public class Board extends JPanel implements ActionListener { //Class Board
private TileLayer l; //Instance of TileLayer class
private Menu m; //Instance of menu class
private Player p; //Instance of player class
Timer time; //A timer
public static enum STATE {MENU,GAME}; //The game states
public static STATE State = STATE.MENU; //Set the first state to menu
//END
//GLOBAL
//DECLARATIONS
public Board() {
l = TileLayer.FromFile("D:/ICS3U1/EvilMario/map.txt"); //Tile map data from .txt file
this.addMouseListener(new MouseInput()); //Listen for mouse input
this.addKeyListener(new AL()); //Listen for key input
p = new Player(); //Start running Player class
m = new Menu(); //Start running Menu class
setFocusable(true); //Allows movement
time = new Timer(20,this); //Timer set to update "this" class every 20 milliseconds(Approximately 50fps)
time.start(); //Actually start the timer
}
public void actionPerformed(ActionEvent e) {
p.move(); //Call the move method from the player class
repaint(); //Repaint
}
public void paintComponent(Graphics g) { //Graphics method
super.paintComponent(g); //Super hero?
Graphics2D g2d = (Graphics2D) g; //Cast 2D graphics
if(State==STATE.GAME) {
if(p.distanceTraveled<300)l.DrawLayer(g,0);else l.DrawLayer(g, -(p.distanceTraveled-300)); //Draw the tile map
g2d.drawImage(p.getImage(), p.getX(), p.getY(), 48, 48, null); //Draw the player
if(p.distanceTraveled==3488) System.out.println("You have won the game!"); //Draw the end game screen
} else {
m.render(g); //Render the menu
}
}
private class AL extends KeyAdapter { //Action Listener extends key adapter
public void keyPressed(KeyEvent e) { //On key press
p.keyPressed(e); //Send whatever key was pressed TO the keyPressed method in the player class
}
public void keyReleased(KeyEvent e) { //On key release
p.keyReleased(e); //Send whatever key was released TO the keyReleased method in the player class
}
}
}
Player.java(player logic)
package EvilMario; //Include this class in the EvilMario game package
import java.awt.Image;
import java.awt.event.KeyEvent;
import javax.swing.ImageIcon;
public class Player {
int x, dx, y, distanceTraveled; //x coordinate,change in x coordinate,y coordinate,1st rep bg,2nd rep bg,dist traveled
Image player; //The player variable
ImageIcon walk_L_anim = new ImageIcon("D:/ICS3U1/EvilMario/images/animatedMario/walk_L_anim.gif");
ImageIcon walk_L_idle = new ImageIcon("D:/ICS3U1/EvilMario/images/animatedMario/walk_L_idle.png");
ImageIcon walk_R_anim = new ImageIcon("D:/ICS3U1/EvilMario/images/animatedMario/walk_R_anim.gif");
ImageIcon walk_R_idle = new ImageIcon("D:/ICS3U1/EvilMario/images/animatedMario/walk_R_idle.png");
ImageIcon jump_L_anim = new ImageIcon("D:/ICS3U1/EvilMario/images/animatedMario/jump_L_anim.gif");
ImageIcon jump_L_idle = new ImageIcon("D:/ICS3U1/EvilMario/images/animatedMario/jump_L_idle.png");
ImageIcon jump_R_anim = new ImageIcon("D:/ICS3U1/EvilMario/images/animatedMario/jump_R_anim.gif");
ImageIcon jump_R_idle = new ImageIcon("D:/ICS3U1/EvilMario/images/animatedMario/jump_R_idle.png");
boolean holdingLeft = false;
boolean holdingRight = false;
static boolean jumping = false;
static boolean falling = false;
static int jumpingTime = 350;
public Player() {
player = walk_R_idle.getImage(); //Give the player the image
x = 75; //The original x position of the player
y = 277; //The original y position of the player
distanceTraveled = 75; //Original distance traveled
}
public void move() {
if(x>=0 && x<=300) { //If the player is within the moving area
x = x+dx; //The x position is updated to become itself+the amount you moved
}
if(x<0) //If the player has reached he very left side of the screen(0px)
x=0; //Move him up a pixel so he can move again
if(x>300) //If the player has reached the center of the screen(300px)
x=300; //Move him down a pixel so he can move again
distanceTraveled=distanceTraveled+dx; //Calculate distanceTraveled
if(distanceTraveled<0) //Make sure distanceTraveled isn't a negative
distanceTraveled=0; //Make sure distanceTraveled isn't a negative
if(distanceTraveled>=300) //Keep player at center position once past 300 mario meters
x=300; //Keep player at center position once past 300 mario meters
if(holdingLeft && !holdingRight) {
if(distanceTraveled<300)dx=-5; else dx=-4;
if(jumping && !falling) {
player = jump_L_anim.getImage();
y-=8;
} else {
player = walk_L_anim.getImage();
if(y<277)
y+=8;
}
} else if(holdingRight && !holdingLeft) {
if(distanceTraveled<300)dx=5; else dx=4;
if(jumping && !falling) {
player = jump_R_anim.getImage();
y-=8;
} else {
player = walk_R_anim.getImage();
if(y<277)
y+=8;
}
} else if(!holdingRight && !holdingLeft) {
dx = 0;
if(jumping && !falling) {
player = jump_R_anim.getImage();
y-=8;
} else {
if(y<277)
y+=8;
}
}
if(y==277) {
falling = false;
}
System.out.println("LEFT: "+holdingLeft+" JUMP: "+jumping+" RIGHT: "+holdingRight+" FALLING: "+falling+" Y: "+y);
}
public int getX() { return x; } //This method will return the x. Is used by other classes
public int getY() { return y; } //This method will return the y. Is used by other classes
public Image getImage() { return player; } //This method will return the player. Is used by other classes
public void keyPressed(KeyEvent e) { //Called from the board class, the argument is whatever key was pressed
int key = e.getKeyCode(); //The key originally sent from the board class
if(key == KeyEvent.VK_LEFT && !holdingLeft)
holdingLeft = true;
if(key == KeyEvent.VK_RIGHT && !holdingRight)
holdingRight = true;
if(key == KeyEvent.VK_UP && !jumping && !falling)
new Thread(new JumpThread(this)).start();
}
public void keyReleased(KeyEvent e) { //Called from the board class, the argument is whatever key was released
int key = e.getKeyCode(); //The key originally sent from the board class
if(key == KeyEvent.VK_LEFT) { //If the left or right key was released
dx = 0; //Stop moving
holdingLeft = false;
player = walk_L_idle.getImage();
}
if(key == KeyEvent.VK_RIGHT) {
dx = 0;
holdingRight = false;
player = walk_R_idle.getImage();
}
}
}
TileLayer.java (Rendering of the tile layer)(Probably most important part relating to the question)
package EvilMario; //Include this class in the EvilMario game package
import java.awt.Graphics; //
public class TileLayer {
private int[][] map; //2D array
private BufferedImage tileSheet; //The tile sheet
public TileLayer(int[][] existingMap) { //
map = new int[existingMap.length][existingMap[0].length]; //map initialized
for(int y=0;y<map.length;y++) { //Loop through all boxes
for(int x=0;x<map[y].length;y++) { //Loop through all boxes
map[y][x] = existingMap[y][x]; //Update the map
}
}
tileSheet = LoadTileSheet("D:/ICS3U1/EvilMario/images/tilemap.gif"); //Load the tilesheet
}
public TileLayer(int width, int height) {
map = new int[height][width];
}
public static TileLayer FromFile(String fileName) {
TileLayer layer = null;
ArrayList<ArrayList<Integer>> tempLayout = new ArrayList<>();
try (BufferedReader br = new BufferedReader(new FileReader(fileName))) {
String currentLine;
while((currentLine = br.readLine()) !=null) {
if(currentLine.isEmpty())
continue;
ArrayList<Integer> row = new ArrayList<>();
String[] values = currentLine.trim().split(" ");
for(String string: values) {
if(!string.isEmpty()) {
int id = Integer.parseInt(string);
row.add(id);
}
}
tempLayout.add(row);
}
} catch(IOException e) {
System.out.println("ERROR");
}
int width = tempLayout.get(0).size();
int height = tempLayout.size();
layer = new TileLayer(width,height);
for(int y=0;y<height;y++) {
for(int x=0;x<width;x++) {
layer.map[y][x] = tempLayout.get(y).get(x);
}
}
layer.tileSheet = layer.LoadTileSheet("D:/ICS3U1/EvilMario/images/tilemap.gif");
return layer;
}
public BufferedImage LoadTileSheet(String fileName) {
BufferedImage img = null;
try {
img = ImageIO.read(new File(fileName));
} catch(Exception e) {
System.out.println("Could not load image");
}
return img;
}
int scale = 2;
public void DrawLayer(Graphics g, int position) {
for(int y=0;y<map.length;y++) {
for(int x=0;x<map[y].length;x++) {
int index = map[y][x];
int yOffset = 0;
if(index>(tileSheet.getWidth() / Engine.TILE_WIDTH) -1) {
yOffset++;
index = index - (tileSheet.getWidth() / Engine.TILE_WIDTH);
}
g.drawImage(tileSheet,
((x * Engine.TILE_WIDTH)*scale)+position,
((y * Engine.TILE_HEIGHT)*scale),
(((x * Engine.TILE_WIDTH) + Engine.TILE_WIDTH )*scale)+position,
(((y * Engine.TILE_HEIGHT) + Engine.TILE_HEIGHT)*scale),
index * Engine.TILE_WIDTH,
yOffset * Engine.TILE_HEIGHT,
(index * Engine.TILE_WIDTH) + Engine.TILE_WIDTH,
(yOffset * Engine.TILE_HEIGHT) + Engine.TILE_HEIGHT,
null
);
}
}
}
}
Engine.java (Not as important)(Simple variables for tile sizes)
package EvilMario;
public class Engine {
public static final int TILE_WIDTH = 16;
public static final int TILE_HEIGHT = 16;
}
If you need other pieces of code, just ask for them. I am not asking you to give me a specific answer to the question but simply a method that would work with my following code.
A specific answer would be nice though :)
I also believe the answer to this question will be useful to others because this method was explained in a popular java 2d game tutorial video(They never showed hit detection).
Methods I tried:
Creating a new java file called HitDetectionLayer with the exact code in TileLayer.java that stored positions in arrays. It failed :(
Ok, I'm not entirely sure what you are doing, if you throw up some images it would be more clear.
At any rate, 'hit detection' aka collision detection is a very complex topic, but it depends on what you want to do. If you want everything to be boxes or circles, then it is quite easy. If however you want things to rotate or you want collision for complex shapes it becomes extreme difficult.
Most games use circles or spheres for collision. You put the majority of your graphics (it may not fit perfectly either leaving part of your images in or out of the circle but that's life). Now lets say you have your mario sprite and one of those turtles. Well, you have circles around them both and once the circles touch you trigger your event.
The math for this is very easy because circles are by definition a perimeter around a constant length. Look at this:
You probably already know this, and it may seem obvious, but if you think about it this is what a circle really is: a consistent length in every fathomable direction. The directions are measured in degrees and from there you move on to trigonometry but you don't need that. What you need is coordinance aka vectors. So look at this:
All you need to determine circle collision is the distance between the circles. No matter what angle the circles collide from it does not matter because the distances from the circle's centre are consistent all the way around. Even if the circles are different sizes, it doesn't matter, just account for the radii difference.
Too compute all of this, you would write a method like this:
public boolean testDistanceBetween( float radius1, float radius2,
float x1, float x2, float y1, float y2 ){
double distanceBetween = Math.sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2));
if(distanceBetween < (radius1+radius2) ){
return true;
}
return false;
}
The moral of the story is that circles are just good that way. If you want to do rectangle collision you take the bottom-left and top right point and you test if other rectangles are in between those points. This should be pretty straight forward, each point is a vector, each rectangle has 4 points. If any of the 4 points of one rectangle are between points on the other rectangle, there is collision.
You can use this system to handle ground and walls also. For example, if ground is at Y=300, then if your sprite's y coordinance are == 300, you suspend your gravity.
The main thing I wanted to explain is that if you intend to have rotating rectangles or polygons and you want to detect collision on them... good luck. It can be done yes, but you should understand you are implementing complex physics, especially when/if you implement gravity.
So my answer is cautionary: there is NO easy way to detect collision of rotating rectangles or polygons. Circles and static rectangles are the limits. If you really want to do rotating rectangles/polygons get a physics engine. Box2d is pretty good and has a Java version Jbox2d.
i just finish making a Minesweeper game, everything functions perfectly except one thing, the speed of loading the images into he game. I noticed if i have a large number of cells in the game images loads really slow after the mouse click on the cell and it gets faster if i have a smaller number of cells. is there any other way that would make loading images much faster than the one i used? here is the method i used in order to load the images into the game :
private void draw(Graphics g) {
BufferedImage gRec =null, flag=null, mine=null, aCount0=null,
aCount1=null,aCount2 =null,aCount3 =null,aCount4 =null,aCount5 =null,
aCount6 =null,aCount7 =null,aCount8 = null;
try {
gRec = ImageIO.read(new File("/Users/msa_666/Desktop/blank.gif"));
flag = ImageIO.read(new File("/Users/msa_666/Desktop/bombflagged.gif"));
mine = ImageIO.read(new File("/Users/msa_666/Desktop/bombdeath.gif"));
flag = ImageIO.read(new File("/Users/msa_666/Desktop/bombflagged.gif"));
aCount0 = ImageIO.read(new File("/Users/msa_666/Desktop/open0.gif"));
aCount1 = ImageIO.read(new File("/Users/msa_666/Desktop/open1.gif"));
aCount2 = ImageIO.read(new File("/Users/msa_666/Desktop/open2.gif"));
aCount3 = ImageIO.read(new File("/Users/msa_666/Desktop/open3.gif"));
aCount4 = ImageIO.read(new File("/Users/msa_666/Desktop/open4.gif"));
aCount5 = ImageIO.read(new File("/Users/msa_666/Desktop/open5.gif"));
aCount6 = ImageIO.read(new File("/Users/msa_666/Desktop/open6.gif"));
aCount7 = ImageIO.read(new File("/Users/msa_666/Desktop/open7.gif"));
aCount8 = ImageIO.read(new File("/Users/msa_666/Desktop/open8.gif"));
}
catch (IOException e) {
e.printStackTrace();
}
if (getCovered() == true && getMarked () == false) { // gray rectangle
g.drawImage(gRec,getX(),getY(),w,h,null);
}
else if (getCovered()==true && getMarked() == true) { //flag
g.drawImage(flag,getX(),getY(),w,h,null);
}
else if (getCovered()== false && getMined()== true){ //mine
g.drawImage(mine,getX(),getY(),w,h,null);
}
else if ( getCovered() == false && getMined() == false) { // adjacency count image
switch (getAdjCount()){
case 0:
g.drawImage(aCount0,getX(),getY(),w,h,null);
break;
case 1:
g.drawImage(aCount1,getX(),getY(),w,h,null);
break;
case 2:
g.drawImage(aCount2,getX(),getY(),w,h,null);
break;
case 3:
g.drawImage(aCount3,getX(),getY(),w,h,null);
break;
case 4:
g.drawImage(aCount4,getX(),getY(),w,h,null);
break;
case 5:
g.drawImage(aCount5,getX(),getY(),w,h,null);
break;
case 6:
g.drawImage(aCount6,getX(),getY(),w,h,null);
break;
case 7:
g.drawImage(aCount7,getX(),getY(),w,h,null);
break;
case 8:
g.drawImage(aCount8,getX(),getY(),w,h,null);
break;
}
}
}
here is the mouse listener to repaint each cell after clicking on it :
public void mouseClicked(MouseEvent e) {
int sRow, sCol;
sRow= e.getX() / cellHeight;
sCol = e.getY()/ cellWidth;
System.out.println(e.getX() +"," +sCol);
System.out.println(e.getY()+","+sRow);
if (e.getButton() == MouseEvent.BUTTON1) {
if( cells[sRow][sCol].getMarked() == false)
uncoverCell(cells[sRow][sCol]);
// cells[sRow][sCol].setCovered(false);
System.out.println(cells[sRow][sCol].getMined());
repaint();
}
else if (e.getButton() == MouseEvent.BUTTON2) {
}
else if (e.getButton() == MouseEvent.BUTTON3) {
if (cells[sRow][sCol].getMarked() == false){
cells[sRow][sCol].setMarked(true);
repaint();
}
else {
cells[sRow][sCol].setMarked(false);
repaint();
}
}
if (allMinesMarked() && allNonMinesUncovered()){
System.out.println("You Win");
}
}
public void paintComponent(Graphics g) {
for ( int i=0 ; i <rowCount; i++ ) {
for (int j=0; j<columnCount; j++) {
cells[i][j].draw(g);
}
}
}
You need to tell us:
Just where is draw(...) called?
How do you obtain the Graphics object, g, that is passed into the draw method's parameter?
I'm guessing here since we don't have all of the relevant code, but it looks as if you're re-reading in your images each time you want to display one. If so, don't do this. Read the images in only once at the start of the program, and then use the Images or perhaps better, ImageIcons, obtained when you need them.
Edit
Thanks for posting more code, and this in fact confirms my suspicion: you're re-reading in the image files with every repaint of your GUI. This is highly inefficient and will slow your program down to a crawl. Again, you should read the images into your program once and then use them multiple times.
Myself I'd create ImageIcons from the images, and then display them in a JLabel. When there is need to swap images, simply call setIcon(...) on the JLabel. This way there's no need to even mess with paintComponent(...).
Edit 2
For example (compile and run this):
import java.awt.GridLayout;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.imageio.ImageIO;
import javax.swing.*;
public class SwapIcons {
private static final int CELL_SIDE_COUNT = 3;
private ImageCell[] imageCells = new ImageCell[CELL_SIDE_COUNT * CELL_SIDE_COUNT];
private JPanel mainPanel = new JPanel();
public SwapIcons(final GetImages getImages) {
mainPanel.setLayout(new GridLayout(CELL_SIDE_COUNT, CELL_SIDE_COUNT));
mainPanel.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
for (int i = 0; i < imageCells.length; i++) {
imageCells[i] = new ImageCell(getImages);
mainPanel.add(imageCells[i].getImgLabel());
}
}
public JComponent getMainComponent() {
return mainPanel;
}
private static void createAndShowGui(GetImages getImages) {
SwapIcons swapIcons = new SwapIcons(getImages);
JFrame frame = new JFrame("Click on Icons to Change");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.getContentPane().add(swapIcons.getMainComponent());
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
public static void main(String[] args) {
try {
final GetImages getImages = new GetImages();
SwingUtilities.invokeLater(new Runnable() {
public void run() {
createAndShowGui(getImages);
}
});
} catch (IOException e) {
e.printStackTrace();
}
}
}
class ImageCell {
private JLabel imgLabel = new JLabel();
private GetImages getImages;
private int iconIndex = 0;
public ImageCell(final GetImages getImages) {
this.getImages = getImages;
imgLabel.setIcon(getImages.getIcon(0));
imgLabel.addMouseListener(new MyMouseListener());
}
public JLabel getImgLabel() {
return imgLabel;
}
private class MyMouseListener extends MouseAdapter {
#Override
public void mousePressed(MouseEvent e) {
iconIndex++;
iconIndex %= getImages.getIconListSize();
imgLabel.setIcon(getImages.getIcon(iconIndex));
}
}
}
// Simply gets a SpriteSheet and subdivides it into a List of ImageIcons
class GetImages {
private static final String SPRITE_PATH = "http://th02.deviantart.net/"
+ "fs70/PRE/i/2011/169/0/8/blue_player_sprite_sheet_by_resetado-d3j7zba.png";
public static final int SPRITE_ROWS = 6;
public static final int SPRITE_COLS = 6;
public static final int SPRITE_CELLS = 35;
private List<ImageIcon> iconList = new ArrayList<ImageIcon>();
public GetImages() throws IOException {
URL imgUrl = new URL(SPRITE_PATH);
BufferedImage mainImage = ImageIO.read(imgUrl);
for (int i = 0; i < SPRITE_CELLS; i++) {
int row = i / SPRITE_COLS;
int col = i % SPRITE_COLS;
int x = (int) (((double) mainImage.getWidth() * col) / SPRITE_COLS);
int y = (int) ((double) (mainImage.getHeight() * row) / SPRITE_ROWS);
int w = (int) ((double) mainImage.getWidth() / SPRITE_COLS);
int h = (int) ((double) mainImage.getHeight() / SPRITE_ROWS);
BufferedImage img = mainImage.getSubimage(x, y, w, h);
ImageIcon icon = new ImageIcon(img);
iconList.add(icon);
}
}
// get the Icon from the List at index position
public ImageIcon getIcon(int index) {
if (index < 0 || index >= iconList.size()) {
throw new ArrayIndexOutOfBoundsException(index);
}
return iconList.get(index);
}
public int getIconListSize() {
return iconList.size();
}
}
Hovercraft Full Of Eels answer is good and will work.
And is fine for a standalone app, but for an applet or web start app can further optimize by having one large image and then copying parts of it to the graphics object that is visible, think grids and use function in java.awt.Graphics object (from javadoc):
public abstract boolean drawImage(Image img,
int dx1,
int dy1,
int dx2,
int dy2,
int sx1,
int sy1,
int sx2,
int sy2,
ImageObserver observer)
Draws as much of the specified area of the specified image as is currently available, scaling it on the fly to fit inside the specified area of the destination drawable surface. Transparent pixels do not affect whatever pixels are already there.
This method returns immediately in all cases, even if the image area to be drawn has not yet been scaled, dithered, and converted for the current output device. If the current output representation is not yet complete then drawImage returns false. As more of the image becomes available, the process that loads the image notifies the specified image observer.
This method always uses the unscaled version of the image to render the scaled rectangle and performs the required scaling on the fly. It does not use a cached, scaled version of the image for this operation. Scaling of the image from source to destination is performed such that the first coordinate of the source rectangle is mapped to the first coordinate of the destination rectangle, and the second source coordinate is mapped to the second destination coordinate. The subimage is scaled and flipped as needed to preserve those mappings.
Parameters:
img - the specified image to be drawn. This method does nothing if img is null.
dx1 - the x coordinate of the first corner of the destination rectangle.
dy1 - the y coordinate of the first corner of the destination rectangle.
dx2 - the x coordinate of the second corner of the destination rectangle.
dy2 - the y coordinate of the second corner of the destination rectangle.
sx1 - the x coordinate of the first corner of the source rectangle.
sy1 - the y coordinate of the first corner of the source rectangle.
sx2 - the x coordinate of the second corner of the source rectangle.
sy2 - the y coordinate of the second corner of the source rectangle.
observer - object to be notified as more of the image is scaled and converted.
Returns:
false if the image pixels are still changing; true otherwise.
This is better as it takes a few seconds to make a new connection and download image over internet, so if you have one main image that has all the sub images in a big table then total time to download, load and render will be less. extra logic to copy from an area is trivial maybe .1KB of jar file space :)